SQL'de yapmak kolay değil ama imkansız değil. Bunun yalnızca DDL aracılığıyla uygulanmasını istiyorsanız, DBMS'nin DEFERRABLE
kısıtlamaları uygulaması gerekir . Bu yapılabilir (ve bunları uygulayan Postgres'te çalışmak için kontrol edilebilir):
-- lets create first the 2 tables, A and B:
CREATE TABLE a
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
Buraya kadar her "normal" tasarım vardır A
sıfır, bir çoğu ya bağlı olabilir B
ve her B
sıfır, bir ya da birçok ilişkili olabilir A
.
"Toplam katılım" kısıtlamasının ters sırada ( sırasıyla referans olarak A
ve B
referans olarak R
) kısıtlamalara ihtiyacı vardır . FOREIGN KEY
Zıt yönlerde (X'den Y'ye ve Y'den X'a) kısıtlamalara sahip olmak bir daire (bir "tavuk ve yumurta" sorunu) oluşturuyor ve bu yüzden en azından bunlardan birine ihtiyacımız var DEFERRABLE
. Bu durumda iki çemberimiz var ( A -> R -> A
ve B -> R -> B
bu nedenle iki ertelenebilir kısıtlamaya ihtiyacımız var:
-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
ALTER TABLE b
ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
Sonra veri ekleyebileceğimizi test edebiliriz. INITIALLY DEFERRED
Gerekli olmadığını unutmayın . Kısıtlamaları şu şekilde tanımlayabilirdik, DEFERRABLE INITIALLY IMMEDIATE
ancak SET CONSTRAINTS
işlem sırasında bunları ertelemek için ifadeyi kullanmamız gerekirdi . Yine de her durumda, tablolara tek bir işlemde eklememiz gerekir:
-- insert data
BEGIN TRANSACTION ;
INSERT INTO a (aid, bid)
VALUES
(1, 1), (2, 5),
(3, 7), (4, 1) ;
INSERT INTO b (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7) ;
INSERT INTO r (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7), (4, 1),
(4, 2), (4, 7) ;
END ;
SQLfiddle'da test edildi .
DBMS'de DEFERRABLE
kısıtlamalar yoksa , bir geçici çözüm A (bid)
ve B (aid)
sütunları olarak tanımlamaktır NULL
. INSERT
Prosedürler / tablolar daha sonra birinci içine inserte sahip olacak A
ve B
(boş değerlere koyarak bid
ve aid
daha sonra, sırasıyla) içine girecek R
ve daha sonra, ilgili boş olmayan değerlere üzerinde boş değerleri güncelleştirmek R
.
Bu yaklaşımla, DBMS DDL tarafından gereksinimleri yalnız ama her zorlamaz INSERT
(ve UPDATE
ve DELETE
ve MERGE
prosedür olarak kabul edilir ve buna göre ayarlanır ve kullanıcılar sadece onları değil tablolarına direkt yazma erişimine sahip kullanmaya kısıtlı olmak zorunda zorundadır).
FOREIGN KEY
Kısıtlamalarda çemberlere sahip olmak birçok iyi uygulama tarafından kabul edilmez ve iyi nedenlerle karmaşıklık bunlardan biridir. Örneğin ikinci yaklaşımla (boş değerli sütunlarla), DBMS'ye bağlı olarak satırların güncellenmesi ve silinmesinin yine de ekstra kodla yapılması gerekecektir. Örneğin SQL Server'da ON DELETE CASCADE
, FK çevreleri olduğunda basamaklı güncellemelere ve silme işlemlerine izin verilmediği için yalnızca koyamazsınız .
Lütfen bu ilgili sorunun cevaplarını da okuyun:
Ayrıcalıklı bir çocukla birebir ilişkiniz nasıl olur?
Başka bir 3. yaklaşım (yukarıda belirtilen soruya cevabım bakın) dairesel FK'ları tamamen kaldırmaktır. Yani, kodun ilk kısmını tutarak (tablolarla A
, B
, R
sadece R A ve B ve yabancı tuşları) (aslında basitleştirilmesi) neredeyse bozulmamış, biz başka tablo eklemek A
gelen "olması gereken bir" ilgili öğeyi saklamak için B
. Böylece, A (bid)
sütun A_one (bid)
B'ye hareket eder. B'den A'ya ters ilişki için aynı işlem yapılır:
CREATE TABLE a
( aid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
CREATE TABLE a_one
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_one_pk PRIMARY KEY (aid),
CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
);
CREATE TABLE b_one
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_one_pk PRIMARY KEY (bid),
CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
);
1. ve 2. yaklaşım arasındaki fark, dairesel FK'ler olmamasıdır, bu nedenle basamaklı güncellemeler ve silme işlemleri iyi çalışır. "Toplam katılımın" uygulanması, 2. yaklaşımda olduğu gibi yalnızca DDL tarafından gerçekleştirilmez ve uygun prosedürlerle ( INSERT/UPDATE/DELETE/MERGE
) yapılmalıdır . 2. yaklaşımla küçük bir fark, tüm sütunların boş bırakılamaz olarak tanımlanabilmesidir.
Başka bir 4. yaklaşım ( yukarıda belirtilen sorudaki @Aaron Bertrand'ın cevabına bakınız), DBMS'nizde mevcutsa filtrelenmiş / kısmi benzersiz dizinler kullanmaktır ( R
bu durumda, bunlardan ikisine ihtiyacınız olacak ). Bu, 2 yaklaşıma çok benzer, ancak 2 ekstra tabloya ihtiyacınız yoktur. "Toplam katılım" kısıtlaması hala kodla uygulanmalıdır.