Вам не нужны триггеры или PL / pgSQL вообще.
Вам даже не нужны DEFERRABLE ограничения.
И вам не нужно хранить любую информацию с избыточностью.
Включите идентификатор активной электронной почты в usersтаблицу, что приведет к взаимным ссылкам. Кто-то может подумать, что нам нужно 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 Fiddle.
Вы можете инкапсулировать команды SQL в функции на стороне сервера, если какой-то простой ORM недостаточно умен, чтобы справиться с этим.
Тесно связаны, с достаточным объяснением:
Также связано:
Об DEFERRABLEограничениях:
О nextval()и pg_get_serial_sequence():