web-dev-qa-db-ja.com

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;

次に、CHECK制約から呼び出されます

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

標準SQLで遅延可能な制約を作成するには、次のようになります。

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

PostgreSQLで同じことをするにはどうすればよいですか?

15
Radovan Luptak

Postgresqlでは、他のRDBMSと同じ方法で制約を延期できますが、現在のバージョン(9.2)では、UNIQUE、PRIMARY KEY、EXCLUDE、およびREFERENCESのみを延期できます。マニュアルの _this page_ からの抜粋:

DEFERRABLE
_NOT DEFERRABLE_

これは、制約を延期できるかどうかを制御します。延期できない制約は、すべてのコマンドの直後にチェックされます。延期可能な制約のチェックは、トランザクションが終了するまで延期できます(SET CONSTRAINTSコマンドを使用)。 NOTDEFERRABLEがデフォルトです。現在、UNIQUE、PRIMARY KEY、EXCLUDE、およびREFERENCES(外部キー)制約のみがこの句を受け入れます。 NOTNULLおよびCHECK制約は延期できません。

_INITIALLY IMMEDIATE_
_INITIALLY DEFERRED_

制約が延期可能である場合、この句は制約をチェックするデフォルトの時間を指定します。制約がINITIALLYIMMEDIATEの場合、各ステートメントの後にチェックされます。これがデフォルトです。制約がINITIALLYDEFERREDの場合、トランザクションの終了時にのみチェックされます。制約チェック時間は、SETCONSTRAINTSコマンドで変更できます。

現在の制約の代わりに、_member_details_からaddressまでの単純な遅延外部キーを作成して、すべてのメンバーがアドレスを持っているかどうかを確認できます。

更新:2つの外部キーを作成する必要があります。 address(member_id)からmember_details(member_id)までの通常の1つ。もう1つは、member_details(member_id)からaddress(member_id)に延期されました。

この2つの外部キーを使用すると、次のことができるようになります。

  1. _member_details_にメンバーを作成します。
  2. 手順1のメンバーのアドレスをaddressに作成します
  3. コミット(エラーなし)

[〜#〜]または[〜#〜]

  1. _member_details_にメンバーを作成します。
  2. コミットします(そして、遅延外部キーからエラーを取得します)。
16

クエリをトランザクションでラップし、少なくとも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の会社と上司の連絡先すべての住所を変更しました。そのうちの1人が会社Bに切り替えたときです。ええ、UIがOutlookとは異なる動作をしたときに実際に起こるのを見ました...

とにかく、そしてfwiw、私は通常、連絡先と同じテーブル、つまりaddress1、address2、email1、email2などにこのようなものを保存する方が便利であることがわかりました。他のさまざまな理由で他のものが簡単になります-つまり、調べているようなチェックを実行します。このような情報を2つ以上保存したいという非常にまれなケースは、実際には、面倒なだけの価値はありません。

2

これが私が思いついたものです。

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();

トリガーなしで両方のテーブルで外部キーを使用してIgorのバージョンを試しました。この場合、この制約は延期されません。

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」のヌル値は非ヌル制約に違反しています

この匿名ブロックを使用して挿入する場合:

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
$$;

アドレステーブル(Igorのバージョン)のaddress_idを使用してmember_detailsへの必須の参加を強制することに関する別の問題は、これにより、行をmember_detailsに挿入して既存のアドレス行を参照できるが、既存のアドレス行は異なるmember_details行を参照できることです。後者のmember_details行が削除されると、アドレス行がカスケードされて削除されます。これにより、新しく挿入されたmember_details行を削除できる場合とできない場合があります(設定によって異なります)。また、member_idとaddress_idで参加すると、異なる詳細が返されます。したがって、別の制約が必要になるため、トリガーを延期しないため、トリガーを使用して挿入前にドロップし、挿入後に再作成しました。

1
Radovan Luptak