การปรับใช้ความสัมพันธ์แบบหลายต่อหลายคนกับข้อ จำกัด การมีส่วนร่วมโดยรวมใน SQL


17

ฉันควรนำไปใช้ใน SQL สถานการณ์ที่อธิบายไว้ในแผนภาพความสัมพันธ์ของเอ็นติตี้ต่อไปนี้อย่างไร?

ความสัมพันธ์หลายต่อหลายคนที่มีข้อ จำกัด การมีส่วนร่วมทั้งหมด

ในขณะที่มันจะแสดงทุกAประเภทกิจการที่เกิดขึ้นจะต้องเกี่ยวข้องกับอย่างน้อยหนึ่ง Bคู่ (แสดงโดยเส้นเชื่อมต่อคู่) และในทางกลับกัน ฉันรู้ว่าฉันควรสร้างสามตารางที่ตามมา:

    CREATE TABLE A
    (
        a INT NOT NULL,
        CONSTRAINT A_PK PRIMARY KEY (a)
    );

    CREATE TABLE B
    (
        b INT NOT NULL,
        CONSTRAINT B_PK PRIMARY KEY (b)
    );

    CREATE TABLE R
    (
        a INT NOT NULL,
        b INT NOT NULL,
        CONSTRAINT R_PK      PRIMARY KEY (a, b),
        CONSTRAINT R_to_A_FK FOREIGN KEY (a)
            REFERENCES A (a),
        CONSTRAINT R_to_B_FK FOREIGN KEY (b)
            REFERENCES B (b)
    );

แต่สิ่งที่เกี่ยวกับการดำเนินการตามข้อ จำกัด การมีส่วนร่วมทั้งหมด (เช่นบังคับให้แต่ละตัวอย่างของAหรืออย่างใดอย่างหนึ่งหรือBมีส่วนร่วมในอย่างน้อยหนึ่งความสัมพันธ์ที่เกิดขึ้นกับอื่น ๆ )?

คำตอบ:


16

ไม่ใช่เรื่องง่ายที่จะทำใน 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 ตัว ข้อ จำกัด "การมีส่วนร่วมทั้งหมด" ยังคงมีการใช้งานโดยรหัส


วิธีที่ 4 (ซ่อนอยู่เล็กน้อย) สมบูรณ์แบบจริงๆ ตัวอย่างเช่นดูpostgresql.org/docs/9.6/static/indexes-partial.htmlตัวอย่าง 11-3 สำหรับ postgres
Danilo

@Danilo ฉันเห็นว่ามันสมบูรณ์แบบอย่างไรเพื่อให้แน่ใจว่ามีการเข้าร่วมทั้งหมดสูงสุด 1 ครั้ง (ขึ้นอยู่กับฟิลด์เพิ่มเติม - ความสำเร็จในตัวอย่างหลังโพสต์) ฉันไม่เห็นว่ามันจะช่วยให้มั่นใจได้อย่างไรว่ามีอย่างน้อยหนึ่งความสำเร็จ - คำถามจริงในหัวข้อนี้ คุณช่วยอธิบายรายละเอียดได้ไหม?
Alexander Mihailov

3

คุณไม่สามารถโดยตรง สำหรับผู้เริ่มต้นคุณจะไม่สามารถแทรกระเบียนสำหรับ A หากไม่มี B อยู่แล้ว แต่คุณไม่สามารถสร้างบันทึก B ได้หากไม่มีระเบียน A มีหลายวิธีในการบังคับใช้สิ่งต่าง ๆ เช่นทริกเกอร์คุณจะต้องตรวจสอบทุกส่วนแทรกและลบอย่างน้อยหนึ่งระเบียนที่เกี่ยวข้องยังคงอยู่ในตารางลิงก์ AB

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.