ไม่ใช่เรื่องง่ายที่จะทำใน 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
คนและทุกคนสามารถที่เกี่ยวข้องกับศูนย์หนึ่งหรือหลายB
A
ข้อ จำกัด "การมีส่วนร่วมทั้งหมด" ต้องการข้อ จำกัด ในลำดับย้อนกลับ (จาก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 ข้างต้นเป็นค่าที่ไม่เป็นโมฆะเกี่ยวข้องจากNULL
INSERT
A
B
bid
aid
R
R
ด้วยวิธีการนี้ 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 ตัว ข้อ จำกัด "การมีส่วนร่วมทั้งหมด" ยังคงมีการใช้งานโดยรหัส