Hiç tetikleyiciye veya PL / pgSQL'e ihtiyacınız yok. Sınırlamalara
bile ihtiyacınız yok DEFERRABLE
.
Ve herhangi bir bilgiyi gereksiz yere saklamanıza gerek yoktur.
users
Karşılıklı başvurulara neden olan etkin e-postanın kimliğini tabloya ekleyin . Bir DEFERRABLE
kullanıcı ve onun aktif e-postasını eklemenin tavuk ve yumurta problemini çözmek için bir kısıtlamaya ihtiyacımız olduğunu düşünebiliriz , ancak verileri değiştiren CTE'leri kullanarak buna ihtiyacımız bile yok.
Bu , her zaman kullanıcı başına tam olarak bir aktif e-postayı zorlar :
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);
"En fazla bir aktif e-postaya" yapmak için NOT NULL
kısıtlamayı kaldırın users.email_id
. (Kullanıcı başına hala birden fazla e-posta saklayabilirsiniz, ancak hiçbiri "etkin" değildir.)
Sen edebilirsiniz yapmak active_email_fkey
DEFERRABLE
(ayrı komutlar kullanıcı ve e-posta eki daha fazla gecikmeyi sağlamak için aynı işlem), ama bu gerekli değildir .
Endeks kapsamını optimize etmek için user_id
ilk önce UNIQUE
kısıtlamaya girdim email_fk_uni
. Detaylar:
İsteğe bağlı görünüm:
CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);
Yeni kullanıcıları aktif bir e-posta ile nasıl ekleyeceğiniz (gerektiği gibi):
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);
Özel zorluk, bizim ne başlamamız ne user_id
de bizim olmamızdır email_id
. Her ikisi de ilgili tarafından sağlanan seri numaralarıdır SEQUENCE
. Tek bir RETURNING
maddeyle çözülemez (başka bir tavuk-yumurta problemi). Çözüm, aşağıdaki bağlantılı cevapta ayrıntılınextval()
olarak açıklanmaktadır .
Eğer yoksa biliyoruz için ekteki dizinin adını serial
sütununda email.email_id
sen değiştirebilirsiniz:
nextval('email_email_id_seq'::regclass)
ile
nextval(pg_get_serial_sequence('email', 'email_id'))
Yeni bir "etkin" e-postayı şu şekilde eklersiniz:
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.
Bazı basit fikirli ORM bununla başa çıkmak için yeterince akıllı değilse, SQL komutlarını sunucu tarafı işlevlerinde saklayabilirsiniz.
Yakından ilgili, yeterince açıklama ile:
Ayrıca ilgili:
Hakkında DEFERRABLE
kısıtlamalar:
Hakkında nextval()
ve pg_get_serial_sequence()
: