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 userstabel, menghasilkan referensi bersama. Orang mungkin berpikir kita perlu DEFERRABLEkendala 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 NULLbatasan dari users.email_iduntuk 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 DEFERRABLElebih banyak kelonggaran (masukkan pengguna dan email dalam perintah terpisah dari transaksi yang sama ), tetapi itu tidak perlu .
Aku meletakkan user_idpertama di UNIQUEkendala email_fk_uniuntuk 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_idatau tidak email_idmemulai. Keduanya adalah nomor seri yang disediakan dari masing-masing SEQUENCE. Itu tidak bisa diselesaikan dengan satu RETURNINGklausa (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 serialkolom email.email_idAnda 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 DEFERRABLEkendala:
Tentang nextval()dan pg_get_serial_sequence():