ไม่ใช่เรื่องง่ายที่จะทำใน SQL แต่เป็นไปไม่ได้ หากคุณต้องการให้สิ่งนี้บังคับใช้ผ่าน DDL เพียงอย่างเดียว DBMS จะต้องมีDEFERRABLEข้อ จำกัด ในการใช้งาน สิ่งนี้สามารถทำได้ (และสามารถตรวจสอบการทำงานใน Postgres ที่มีการใช้งาน):
-- 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
);
ถึงตรงนี้คือการออกแบบ "ปกติ" ที่ทุกAอาจจะเกี่ยวข้องกับศูนย์หนึ่งหรือหลายBคนและทุกคนสามารถที่เกี่ยวข้องกับศูนย์หนึ่งหรือหลายBA
ข้อ จำกัด "การมีส่วนร่วมทั้งหมด" ต้องการข้อ จำกัด ในลำดับย้อนกลับ (จากAและBอ้างอิงตามลำดับR) มีFOREIGN KEYข้อ จำกัด ในทิศทางตรงข้าม (จาก X เป็น Y และ Y เพื่อ X) เป็นรูปวงกลม (เป็น "ไก่และไข่" ปัญหา) DEFERRABLEและที่ว่าทำไมเราต้องเป็นหนึ่งในพวกเขาอย่างน้อยจะต้องมี ในกรณีนี้เรามีวงกลมสองวง ( A -> R -> AและB -> R -> Bเราจำเป็นต้องมีข้อ จำกัด สองข้อที่เลื่อนออกได้:
-- 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 ;
จากนั้นเราสามารถทดสอบว่าเราสามารถแทรกข้อมูล โปรดทราบว่าINITIALLY DEFERREDไม่จำเป็น เราสามารถกำหนดข้อ จำกัด ได้DEFERRABLE INITIALLY IMMEDIATEแต่จากนั้นเราจะต้องใช้SET CONSTRAINTSคำสั่งเพื่อเลื่อนออกไประหว่างการทำธุรกรรม ในทุกกรณีเราจำเป็นต้องแทรกลงในตารางในการทำธุรกรรมเดียว:
-- 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
ถ้า DBMS ไม่ได้มีDEFERRABLEข้อ จำกัด หนึ่งในการแก้ปัญหาคือการกำหนดA (bid)และคอลัมน์เป็นB (aid) ขั้นตอน / งบแล้วจะต้องใส่ครั้งแรกในและ(วาง nulls ในและตามลำดับ) แล้วใส่ลงไปแล้ว update ค่า null ข้างต้นเป็นค่าที่ไม่เป็นโมฆะเกี่ยวข้องจากNULLINSERTABbidaidRR
ด้วยวิธีการนี้ DBMS ไม่บังคับใช้ข้อกำหนดโดย DDL เพียงอย่างเดียว แต่ทุกขั้นตอนINSERT(และUPDATEและDELETEและMERGE) จะต้องได้รับการพิจารณาและปรับเปลี่ยนให้สอดคล้องและผู้ใช้จะต้องถูก จำกัด ให้ใช้เฉพาะพวกเขาเท่านั้นและไม่สามารถเข้าถึงเขียนโดยตรงไปยังตาราง
การมีแวดวงในFOREIGN KEYข้อ จำกัด ไม่ได้รับการพิจารณาจากแนวปฏิบัติที่ดีที่สุดหลายประการและด้วยเหตุผลที่ดีความซับซ้อนเป็นหนึ่งในนั้น ด้วยวิธีที่สองเช่น (ด้วยคอลัมน์ที่สามารถลบล้างได้) การอัพเดตและการลบแถวจะยังคงต้องทำด้วยรหัสพิเศษขึ้นอยู่กับ DBMS ยกตัวอย่างเช่นใน SQL Server คุณไม่สามารถทำได้ON DELETE CASCADEเพราะการปรับปรุงแบบเรียงซ้อนและการลบไม่ได้รับอนุญาตเมื่อมีแวดวง FK
โปรดอ่านคำตอบของคำถามที่เกี่ยวข้องนี้ด้วย:
จะมีความสัมพันธ์แบบหนึ่งต่อหลายคนกับเด็กที่ได้รับสิทธิพิเศษอย่างไร?
อีกวิธีที่ 3 (ดูคำตอบของฉันในคำถามที่กล่าวถึงข้างต้น) คือการลบ FK แบบวงกลมอย่างสมบูรณ์ ดังนั้นการรักษาส่วนแรกของรหัส (กับตารางA, B, Rและปุ่มต่างประเทศเพียงอย่างเดียวจาก R เพื่อ A และ B) เกือบจะเหมือนเดิม (ที่จริงลดความซับซ้อนของมัน) เราเพิ่มตารางอื่นสำหรับAการจัดเก็บ "ต้องมี" Bรายการที่เกี่ยวข้องจาก ดังนั้นA (bid)คอลัมน์ย้ายไปA_one (bid)ที่เดียวกันจะทำเพื่อความสัมพันธ์ย้อนกลับจาก B ถึง A:
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 และ 2 คือไม่มี FK แบบวงกลมดังนั้นการปรับปรุงและการลบแบบเรียงซ้อนจึงใช้งานได้ดี การบังคับใช้ "การมีส่วนร่วมทั้งหมด" ไม่ใช่โดย DDL เพียงอย่างเดียวเช่นเดียวกับวิธีที่ 2 และจะต้องดำเนินการตามขั้นตอนที่เหมาะสม ( INSERT/UPDATE/DELETE/MERGE) ข้อแตกต่างเล็กน้อยกับวิธีที่ 2 คือคอลัมน์ทั้งหมดสามารถกำหนดได้ไม่เป็นโมฆะ
อีกวิธีที่ 4 (ดูคำตอบของ @Aaron Bertrandในคำถามที่กล่าวถึงข้างต้น) คือการใช้ดัชนีที่ไม่ซ้ำกันกรอง / บางส่วนหากพวกเขามีอยู่ใน DBMS ของคุณ (คุณต้องการสองของพวกเขาในRตารางสำหรับกรณีนี้) นี่คล้ายกับวิธีที่ 3 ยกเว้นว่าคุณไม่ต้องการตารางเสริม 2 ตัว ข้อ จำกัด "การมีส่วนร่วมทั้งหมด" ยังคงมีการใช้งานโดยรหัส