您根本不需要触发器或PL / pgSQL。
您甚至不需要 DEFERRABLE
约束。
而且您不需要冗余地存储任何信息。
在users
表中包括活动电子邮件的ID ,从而产生相互引用。也许有人认为我们需要一个DEFERRABLE
约束来解决插入用户及其活动电子邮件的鸡肋问题,但是使用数据修改CTE甚至不需要。
这始终可以为每位用户强制执行一封有效的电子邮件:
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
,使其成为“最多一封活动的电子邮件”。(您仍然可以为每个用户存储多封电子邮件,但是没有一个是“活动的”。)
你可以让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()
: