2013-05-01 3 views
9

У меня есть функция проверки обязательного участия следующим образом:отложенной проверки ограничений в PostgreSQL

CREATE FUNCTION member_in_has_address() 
RETURNS BOOLEAN AS $$ 
BEGIN 
RETURN EXISTS (SELECT * 
     FROM address a, member_details b 
     WHERE b.member_id = a.member_id); 
END; 
$$ LANGUAGE plpgsql; 

Затем вызывается из проверочного ограничения

ALTER TABLE member_details 
ADD CONSTRAINT member_in_has_address_check 
    CHECK (member_in_has_address()); 

Чтобы создать deferable ограничение в стандартном SQL было бы:

ALTER TABLE member_details 
ADD CONSTRAINT member_in_has_address_check 
    INITIALLY DEFERRED 
    CHECK (member_in_has_address()); 

Как я могу сделать то же самое в PostgreSQL?

+0

Ваш текущий 'member_in_has_address()' будет возвращать 'true', когда ** любой ** из членов имеет адрес. Он не проверяет, имеет ли конкретный член адрес. –

+0

Спасибо, Игорь, но мой главный вопрос заключается в том, как уменьшить такое ограничение до тех пор, пока ребенок ('address') не будет обновлен. Вставка выглядит следующим образом: вставляем в 'member_details', который является родительским, а затем' address', который имеет ключ foreigh. 'member_details' имеет обязательное участие в' address'. –

+0

См. Мой ответ ниже. Ответ - создать дефферентированный внешний ключ. –

ответ

9

Вы можете ограничить ограничения в Postgresql так же, как и в других СУБД, но для текущей версии (9.2) вы можете делить только UNIQUE, PRIMARY KEY, EXCLUDE и ССЫЛКИ. Выписка из this page руководства:

DEFERRABLE
NOT DEFERRABLE

This controls whether the constraint can be deferred. A constraint that is not deferrable will be checked immediately after every command. Checking of constraints that are deferrable can be postponed until the end of the transaction (using the SET CONSTRAINTS command). NOT DEFERRABLE is the default. Currently, only UNIQUE, PRIMARY KEY, EXCLUDE, and REFERENCES (foreign key) constraints accept this clause. NOT NULL and CHECK constraints are not deferrable.

INITIALLY IMMEDIATE
INITIALLY DEFERRED

If a constraint is deferrable, this clause specifies the default time to check the constraint. If the constraint is INITIALLY IMMEDIATE, it is checked after each statement. This is the default. If the constraint is INITIALLY DEFERRED, it is checked only at the end of the transaction. The constraint check time can be altered with the SET CONSTRAINTS command.

Вы можете создать простой отложенный внешний ключ от member_details к address вместо текущего ограничения, чтобы проверить, если каждый член имеет адрес.

ОБНОВЛЕНИЕ: вам необходимо создать 2 внешних ключа. Один регулярный от address(member_id) до member_details(member_id). Другой - от member_details(member_id) до address(member_id).

С этими двумя внешними ключами вы будете иметь возможность:

  1. Создать элемент в member_details.
  2. Создать адрес в address для члена с шага 1
  3. Commit (без ошибок)

ИЛИ

  1. Создать элемент в member_details.
  2. Заключить (и получить ошибку при использовании внешнего ключа).
+0

Спасибо. Таким образом, это означает, что я могу удалить внешний ключ в 'address', а затем вставить сначала в' address', а затем в 'member_details'. –

+0

Но есть и другая проблема: 'member_details' имеет первичный ключ autoincremtned, поэтому сначала вставляю в' member_details', затем извлекаю его первичный ключ, а затем вставляю в 'address'. –

+0

@RadovanLuptak Просто создайте 2 внешних ключа, один 'member_details (member_id)' -> 'address (member_id)' и второй 'address (member_id)' -> 'member_details (member_id)'. –

1

Оберните ваши запросы в транзакции, а затем использовать отложенный внешний ключ и отложили ограничение вызывает, если по крайней мере один адрес необходим:

CREATE CONSTRAINT TRIGGER member_details_address_check_ins 
    AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_details_address_check_ins(); 

ALTER TABLE address 
ADD CONSTRAINT address_member_details_member_id_fkey 
FOREIGN KEY (member_id) REFERENCES member_details(member_id) 
ON UPDATE NO ACTION ON DELETE NO ACTION 
DEFERRABLE INITIALLY DEFERRED; 

CREATE CONSTRAINT TRIGGER address_member_details_check_del 
    AFTER DELETE ON address 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE address_member_details_check_del(); 

-- also consider the update cases for the inevitable merge of duplicate members. 

На отдельную ноте, нормированное и красиво, но положить адреса и контактные данные, такие как электронные письма в отдельной адресной таблице, иногда вводят очень яркие проблемы UI/UX. Например. неподготовленный секретарь, меняющий компанию и адрес всех контактов своего босса в компании A, когда один из них перешел на компанию B. Да, видел, что это происходит по-настоящему, когда пользовательский интерфейс ведет себя иначе, чем Outlook ...

В любом случае, и fwiw, я обнаружил, что обычно удобно хранить этот материал в той же таблице, что и контакт, то есть address1, address2, email1, email2 и т. д. Это упрощает другие вещи по ряду других причин: проверяет, как тот, на который вы смотрите. Чрезвычайно редкий случай, когда вы хотите хранить более двух таких фрагментов информации, на практике просто не стоит хлопот.

+0

Спасибо, я полностью забыл о вставке и удалении триггера для принудительного участия. Я сделал это в Sybase. –

+0

Кстати, сегодня был связанный с этим вопрос, в котором я в конечном итоге расширился на моем заключительном пункте: http://dba.stackexchange.com/a/41430/1860 –

+0

не может совместить с вашим взглядом «присоединиться» к 1: n контакт типа информации в целом (например, адрес, почта, tel, ...) с самим контактом. в приложениях, которые не сосредотачиваются на этом, это может быть хорошо, но как только это более важно для управления контактами часто или в течение длительного периода времени надежно и гибко, обработка этого с отношениями 1: n и отдельными таблицами может быть больше легко разработан и поддерживается в моем опыте. В Outlook это не означает, что в этом случае ИМО не предлагает подход с передовой практикой, поскольку он имеет только довольно базовую модель данных, которая не подходит для более сложного управления контактами. –

0

Это то, что я придумал.

ALTER TABLE address 
ADD CONSTRAINT address_member_in_has_address 
FOREIGN KEY (member_id) REFERENCES member_details(member_id) 
ON DELETE CASCADE 
DEFERRABLE INITIALLY DEFERRED; 

CREATE FUNCTION member_in_has_address() RETURNS trigger AS $BODY$ 
    BEGIN 
    IF NOT EXISTS(SELECT * 
        FROM member_details 
        WHERE member_id IN (SELECT member_id 
             FROM address)) 
    THEN 
      RAISE EXCEPTION 'Error: member does not have address'; 
     END IF; 
    RETURN NEW; 
    END; 
$BODY$ LANGUAGE plpgsql; 

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_ins 
AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_in_has_address(); 

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_del 
AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_in_has_address(); 

Я попробовал версию Игоря, используя внешние ключи в обеих таблицах без триггеров. В этом случае это ограничение не делит.

ALTER TABLE member_details 
ADD CONSTRAINT member_details_in_has_address 
FOREIGN KEY (address_id) REFERENCES address 
ON UPDATE NO ACTION ON DELETE CASCADE 
DEFERRABLE INITIALLY DEFERRED; 

Я получаю это: ОШИБКА: пустое значение в столбце «address_id» нарушает не-нулевое ограничение

При установке с помощью этого annonymous блок:

DO $$ 
DECLARE 
mem BIGINT; 
BEGIN 
INSERT INTO member_details (member_first_name, member_last_name, member_dob, member_phone_no, 
member_email, member_gender, industry_position, account_type, music_interests) 
VALUES ('Rado','Luptak','07/09/80','07540962233','[email protected]','M','DJ','basic','hard core'); 

SELECT member_id 
INTO mem 
FROM member_details 
WHERE member_first_name = 'Rado' AND member_last_name = 'Luptak' 
AND member_dob = '07/09/76'; 

INSERT INTO address (address_id, house_name_no, post_code, street_name, town, country, member_id) 
VALUES (mem, '243', 'E17 3TT','Wood Road','London', 'UK', mem); 

UPDATE member_details 
SET address_id = mem WHERE member_id = mem; 
END 
$$; 

Еще одна проблема, с соблюдения обязательного участия в member_details с использованием address_id таблицы адресов (версия Игоря) заключается в том, что это позволяет мне вставлять строку в member_details и ссылаться на существующую строку адреса, но существующая строка адреса ссылается на другую строку member_details. Когда последняя строка member_details удаляется, она каскадирует и удаляет строку адреса, которая может или не может удалить (зависит от настроек) новую вставленную строку member_details. Он также будет возвращать различные детали при присоединении к member_id и адресу address. Следовательно, для этого требуется другое ограничение, поэтому я остался с триггером и отбрасывал его перед вставкой и воссозданием его после вставки, поскольку триггер не откладывается.

+0

Как я уже говорил, функция 'member_in_has_address()' вернет true, если ** у любого из членов есть адрес. Он не проверяет, имеет ли конкретный член адрес. –

+0

Да, я согласен, и я обновил тело процедуры. –

Смежные вопросы