Non hai bisogno di trigger o PL / pgSQL.
Non hai nemmeno bisogno di DEFERRABLE
vincoli.
E non è necessario archiviare alcuna informazione in modo ridondante.
Includi l'ID dell'email attiva nella users
tabella, risultando in riferimenti reciproci. Si potrebbe pensare che abbiamo bisogno di un DEFERRABLE
vincolo per risolvere il problema del pollo e delle uova dell'inserimento di un utente e della sua e-mail attiva, ma utilizzando CTE che modificano i dati non ne abbiamo nemmeno bisogno.
Ciò impone sempre esattamente un'e-mail attiva per utente :
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);
Rimuovere il NOT NULL
vincolo da users.email_id
per renderlo "al massimo una e-mail attiva". (Puoi comunque memorizzare più e-mail per utente, ma nessuna di esse è "attiva".)
È possibile fare active_email_fkey
DEFERRABLE
per consentire più spazio di manovra (inserto utente e-mail a comandi separati della stessa operazione), ma che è non è necessario .
Ho messo user_id
prima il UNIQUE
vincolo email_fk_uni
per ottimizzare la copertura dell'indice. Dettagli:
Vista opzionale:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Ecco come inserire nuovi utenti con un'e-mail attiva (come richiesto):
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);
La difficoltà specifica è che non abbiamo user_id
né email_id
per cominciare. Entrambi sono numeri di serie forniti dai rispettivi SEQUENCE
. Non può essere risolto con una sola RETURNING
clausola (un altro problema di galline e uova). La soluzione è nextval()
come spiegato in dettaglio nella risposta collegata di seguito .
Se non conosci il nome della sequenza allegata per la serial
colonna email.email_id
puoi sostituire:
nextval('email_email_id_seq'::regclass)
con
nextval(pg_get_serial_sequence('email', 'email_id'))
Ecco come aggiungere una nuova e-mail "attiva":
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.
È possibile incapsulare i comandi SQL nelle funzioni lato server se alcuni ORM semplici non sono abbastanza intelligenti da far fronte a questo.
Strettamente correlato, con ampia spiegazione:
Correlati anche:
Informazioni sui DEFERRABLE
vincoli:
Informazioni nextval()
e pg_get_serial_sequence()
: