Anda tidak perlu pemicu atau PL / pgSQL sama sekali.
Anda bahkan tidak perlu DEFERRABLE
kendala.
Dan Anda tidak perlu menyimpan informasi apa pun secara berlebihan.
Sertakan ID email aktif dalam users
tabel, menghasilkan referensi bersama. Orang mungkin berpikir kita perlu DEFERRABLE
kendala untuk menyelesaikan masalah ayam-dan-telur dari memasukkan pengguna dan emailnya yang aktif, tetapi menggunakan CTE pengubah data kita bahkan tidak memerlukan itu.
Ini memberlakukan tepat satu email aktif per pengguna setiap saat:
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);
Hapus NOT NULL
batasan dari users.email_id
untuk membuatnya "paling banyak satu email aktif". (Anda masih dapat menyimpan beberapa email per pengguna, tetapi tidak ada satu pun yang "aktif".)
Anda dapat membuat active_email_fkey
DEFERRABLE
lebih banyak kelonggaran (masukkan pengguna dan email dalam perintah terpisah dari transaksi yang sama ), tetapi itu tidak perlu .
Aku meletakkan user_id
pertama di UNIQUE
kendala email_fk_uni
untuk cakupan indeks mengoptimalkan. Detail:
Tampilan opsional:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Begini cara Anda memasukkan pengguna baru dengan email aktif (sesuai kebutuhan):
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);
Kesulitan khusus adalah bahwa kita tidak memiliki user_id
atau tidak email_id
memulai. Keduanya adalah nomor seri yang disediakan dari masing-masing SEQUENCE
. Itu tidak bisa diselesaikan dengan satu RETURNING
klausa (masalah ayam dan telur lainnya). Solusinya adalah nextval()
sebagaimana dijelaskan secara terperinci dalam jawaban terkait di bawah .
Jika Anda tidak tahu nama urutan terlampir untuk serial
kolom email.email_id
Anda dapat mengganti:
nextval('email_email_id_seq'::regclass)
dengan
nextval(pg_get_serial_sequence('email', 'email_id'))
Berikut cara Anda menambahkan email "aktif" baru:
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.
Anda mungkin merangkum perintah SQL dalam fungsi sisi server jika beberapa ORM yang berpikiran sederhana tidak cukup pintar untuk mengatasinya.
Terkait erat, dengan banyak penjelasan:
Juga terkait:
Tentang DEFERRABLE
kendala:
Tentang nextval()
dan pg_get_serial_sequence()
: