トリガーやPL / pgSQLはまったく必要ありません。制約
さえ必要あり ませんDEFERRABLE
。
また、情報を重複して保存する必要はありません。
users
テーブルにアクティブな電子メールのIDを含めると、相互参照になります。DEFERRABLE
ユーザーとユーザーのアクティブな電子メールを挿入するという鶏と卵の問題を解決するために制約が必要だと思うかもしれませんが、データ変更CTEを使用すれば、それさえ必要ありません。
これにより、ユーザーごとに常に1つのアクティブな電子メールが常に強制されます。
CREATE TABLE users (
user_id serial PRIMARY KEY
, username text NOT NULL
, email_id int NOT NULL -- FK to active email, constraint added below
);
CREATE TABLE email (
email_id serial PRIMARY KEY
, user_id int NOT NULL REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE
, email text NOT NULL
, CONSTRAINT email_fk_uni UNIQUE(user_id, email_id) -- for FK constraint below
);
ALTER TABLE users ADD CONSTRAINT active_email_fkey
FOREIGN KEY (user_id, email_id) REFERENCES email(user_id, email_id);
NOT NULL
から制約を削除して、users.email_id
「最大で1つのアクティブな電子メール」にします。(ユーザーごとに複数のメールを保存できますが、いずれも「アクティブ」ではありません。)
あなたは可能作るactive_email_fkey
DEFERRABLE
(の別のコマンドでユーザーと電子メールのインサートより余裕できるように、同じトランザクションを)、それはだ必要はありません。
私が入れuser_id
に最初UNIQUE
の制約email_fk_uni
最適化インデックスをカバーします。詳細:
オプションのビュー:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
(必要に応じて)アクティブなメールで新しいユーザーを挿入する方法は次のとおりです。
WITH new_data(username, email) AS (
VALUES
('usr1', 'abc@d.com') -- new users with *1* active email
, ('usr2', 'def3@d.com')
, ('usr3', 'ghi1@d.com')
)
, u AS (
INSERT INTO users(username, email_id)
SELECT n.username, nextval('email_email_id_seq'::regclass)
FROM new_data n
RETURNING *
)
INSERT INTO email(email_id, user_id, email)
SELECT u.email_id, u.user_id, n.email
FROM u
JOIN new_data n USING (username);
具体的な難点は、私たちが最初から持っていuser_id
ないemail_id
ことです。両方ともそれぞれから提供されるシリアル番号ですSEQUENCE
。単一のRETURNING
節(別の鶏と卵の問題)で解決することはできません。ソリューションはnextval()
、以下のリンクされた回答で詳細に説明されています。
あなたがいない場合は知っているために添付の配列の名前serial
欄をemail.email_id
あなたは置き換えることができます。
nextval('email_email_id_seq'::regclass)
と
nextval(pg_get_serial_sequence('email', 'email_id'))
新しい「アクティブな」メールを追加する方法は次のとおりです。
WITH e AS (
INSERT INTO email (user_id, email)
VALUES (3, 'new_active@d.com')
RETURNING *
)
UPDATE users u
SET email_id = e.email_id
FROM e
WHERE u.user_id = e.user_id;
SQLフィドル。
単純なORMがこれに対処するのに十分にスマートでない場合、サーバー側の関数にSQLコマンドをカプセル化できます。
密接な関係があり、十分な説明があります:
関連するもの:
DEFERRABLE
制約について:
:nextval()
pg_get_serial_sequence()