No necesita disparadores o PL / pgSQL en absoluto.
Ni siquiera necesitas DEFERRABLE
restricciones.
Y no necesita almacenar ninguna información de forma redundante.
Incluya la ID del correo electrónico activo en la users
tabla, lo que resulta en referencias mutuas. Uno podría pensar que necesitamos una DEFERRABLE
restricción para resolver el problema del huevo y la gallina de insertar un usuario y su correo electrónico activo, pero usando CTE modificadores de datos ni siquiera necesitamos eso.
Esto aplica exactamente un correo electrónico activo por usuario en todo momento:
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);
Elimine la NOT NULL
restricción de users.email_id
para que sea "como máximo un correo electrónico activo". (Todavía puede almacenar varios correos electrónicos por usuario, pero ninguno de ellos está "activo").
Usted puede hacer active_email_fkey
DEFERRABLE
para permitir más libertad de acción (inserto de usuario y correo electrónico en comandos separados de la misma transacción), pero eso es innecesario .
Puse user_id
primero en la UNIQUE
restricción email_fk_uni
para optimizar la cobertura del índice. Detalles:
Vista opcional:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Así es como inserta nuevos usuarios con un correo electrónico activo (según sea necesario):
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 dificultad específica es que no tenemos user_id
ni email_id
para empezar. Ambos son números de serie proporcionados por los respectivos SEQUENCE
. No se puede resolver con una sola RETURNING
cláusula (otro problema de huevo y gallina). La solución es nextval()
como se explica en detalle en la siguiente respuesta vinculada .
Si no conoce el nombre de la secuencia adjunta para la serial
columna email.email_id
, puede reemplazarla:
nextval('email_email_id_seq'::regclass)
con
nextval(pg_get_serial_sequence('email', 'email_id'))
Así es como agrega un nuevo correo electrónico "activo":
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.
Puede encapsular los comandos SQL en funciones del lado del servidor si algún ORM de mente simple no es lo suficientemente inteligente como para hacer frente a esto.
Estrechamente relacionado, con amplia explicación:
También relacionado:
Sobre DEFERRABLE
restricciones:
Sobre nextval()
y pg_get_serial_sequence()
: