Vous n'avez pas du tout besoin de déclencheurs ou de PL / pgSQL.
Vous n'avez même pas besoin de DEFERRABLE
contraintes.
Et vous n'avez pas besoin de stocker des informations de manière redondante.
Incluez l'ID de l'e-mail actif dans le users
tableau, ce qui entraîne des références mutuelles. On pourrait penser que nous avons besoin d'une DEFERRABLE
contrainte pour résoudre le problème de l'insertion d'un utilisateur et de son e-mail actif, mais en utilisant des CTE modificateurs de données, nous n'en avons même pas besoin.
Cela applique à tout moment exactement un e-mail actif par utilisateur :
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);
Supprimez la NOT NULL
contrainte de users.email_id
pour en faire "au plus un e-mail actif". (Vous pouvez toujours stocker plusieurs e-mails par utilisateur, mais aucun n'est "actif".)
Vous pouvez faire active_email_fkey
DEFERRABLE
pour autoriser plus de latitude (insérer l'utilisateur et l'e-mail dans des commandes distinctes de la même transaction), mais ce n'est pas nécessaire .
Je mets d' user_id
abord dans la UNIQUE
contrainte email_fk_uni
d'optimiser la couverture de l'indice. Détails:
Vue optionnelle:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Voici comment insérer de nouveaux utilisateurs avec un e-mail actif (si nécessaire):
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 difficulté spécifique est que nous n'avons ni user_id
ni email_id
pour commencer. Les deux sont des numéros de série fournis par le respectif SEQUENCE
. Il ne peut pas être résolu avec une seule RETURNING
clause (un autre problème de poulet et d'oeuf). La solution est nextval()
comme expliqué en détail dans la réponse liée ci-dessous .
Si vous ne connaissez pas le nom de la séquence jointe pour la serial
colonne, email.email_id
vous pouvez remplacer:
nextval('email_email_id_seq'::regclass)
avec
nextval(pg_get_serial_sequence('email', 'email_id'))
Voici comment ajouter un nouvel e-mail "actif":
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.
Vous pouvez encapsuler les commandes SQL dans des fonctions côté serveur si certains ORM simples ne sont pas assez intelligents pour y faire face.
Étroitement liés, avec de nombreuses explications:
Également lié:
A propos des DEFERRABLE
contraintes:
À propos nextval()
et pg_get_serial_sequence()
: