Вам не нужны триггеры или 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()
:
ON DELETE CASCADE
? Просто любопытно (пока что работает нормально).Если вы можете добавить столбец в таблицу, следующая схема будет работать почти 1 :
Test SQLFiddle
Перевод с моего собственного SQL Server, с помощью a_horse_with_no_name
Как упомянуто в комментарии ypercube , вы можете даже пойти дальше:
UNIQUE INDEX ON emails (UserID) WHERE (EmailAddress = ActiveAddress)
Эффект тот же, но он, возможно, проще и аккуратнее.
1 Проблема заключается в том, что существующие ограничениях только гарантировать , что строка упоминается как «активные» другая строка существует , не то, что это на самом деле также активно. Я не знаю Postgres достаточно хорошо, чтобы самостоятельно реализовать дополнительное ограничение (по крайней мере, сейчас), но в SQL Server это можно сделать так:
Эти усилия немного улучшают оригинал, используя суррогат, а не дублируя полный адрес электронной почты.
источник
Единственный способ сделать это без изменений схемы - использовать триггер PL / PgSQL.
Для «точно одного» случая вы можете сделать ссылки взаимными, с одним существом
DEFERRABLE INITIALLY DEFERRED
. ТакA.b_id
(FK) ссылкиB.b_id
(PK) иB.a_id
(FK) ссылкиA.a_id
(PK). Многие ORM и т. Д. Не могут справиться с отложенными ограничениями. Таким образом, в этом случае вы бы добавили отложенный FK от пользователя к адресу в столбцеactive_address_id
, вместо использованияactive
флагаaddress
.источник
DEFERRABLE
.