Тригери або 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()
: