เป็นที่ยอมรับได้หรือไม่ที่จะมีการอ้างอิงคีย์ต่างประเทศแบบวงกลม \ จะหลีกเลี่ยงได้อย่างไร?


29

เป็นที่ยอมรับหรือไม่ที่จะมีการอ้างอิงแบบวงกลมระหว่างสองตารางในเขตข้อมูล foreign key?

หากไม่สามารถหลีกเลี่ยงสถานการณ์เหล่านี้ได้อย่างไร

ถ้าเป็นเช่นนั้นข้อมูลจะถูกแทรกได้อย่างไร?

ด้านล่างเป็นตัวอย่างของที่ (ในความคิดของฉัน) อ้างอิงวงกลมจะยอมรับได้:

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

ALTER TABLE Account ADD PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)

2
" ถ้าเป็นเช่นนั้นจะแทรกข้อมูลได้อย่างไร " - ขึ้นอยู่กับ DBMS ที่ใช้งานอยู่ ยกตัวอย่างเช่น Postgres, Oracle, SQLite และ Apache Derby ทำให้ข้อ จำกัด นี้สามารถทำให้เป็นไปได้ ด้วย DBMS อื่น ๆ คุณจะโชคไม่ดี (แต่ฉันยังคงโต้แย้งความต้องการข้อ จำกัด ดังกล่าวในตอนแรก)
a_horse_with_no_name

คำตอบ:


12

เนื่องจากคุณใช้เขตข้อมูล null สำหรับคีย์ต่างประเทศคุณจึงสามารถสร้างระบบที่ทำงานได้อย่างถูกต้องตามที่คุณคาดคิด ในการแทรกแถวลงในตารางบัญชีคุณต้องมีแถวอยู่ในตารางรายชื่อเว้นแต่ว่าคุณจะอนุญาตให้แทรกลงในบัญชีที่มี null PrimaryContactID ในการสร้างแถวที่ติดต่อโดยที่ไม่มีแถวบัญชีอยู่คุณต้องอนุญาตให้คอลัมน์บัญชี ID ในตารางที่ติดต่อว่างเปล่า สิ่งนี้อนุญาตให้บัญชีไม่มีผู้ติดต่อและอนุญาตให้ผู้ติดต่อไม่มีบัญชี บางทีนี่อาจเป็นที่ต้องการ

ต้องบอกว่าการตั้งค่าส่วนตัวของฉันจะต้องมีการตั้งค่าต่อไปนี้:

CREATE TABLE dbo.Accounts
(
    AccountID INT NOT NULL
        CONSTRAINT PK_Accounts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountName VARCHAR(255)
);

CREATE TABLE dbo.Contacts
(
    ContactID INT NOT NULL
        CONSTRAINT PK_Contacts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , ContactName VARCHAR(255)
);

CREATE TABLE dbo.AccountsContactsXRef
(
    AccountsContactsXRefID INT NOT NULL
        CONSTRAINT PK_AccountsContactsXRef
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_AccountID
        FOREIGN KEY REFERENCES dbo.Accounts(AccountID)
    , ContactID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_ContactID
        FOREIGN KEY REFERENCES dbo.Contacts(ContactID)
    , IsPrimary BIT NOT NULL 
        CONSTRAINT DF_AccountsContactsXRef
        DEFAULT ((0))
    , CONSTRAINT UQ_AccountsContactsXRef_AccountIDContactID
        UNIQUE (AccountID, ContactID)
);

CREATE UNIQUE INDEX IX_AccountsContactsXRef_Primary
ON dbo.AccountsContactsXRef(AccountID, IsPrimary)
WHERE IsPrimary = 1;

สิ่งนี้ให้ความสามารถในการ:

  1. อธิบายความสัมพันธ์ระหว่างผู้ติดต่อและบัญชีอย่างชัดเจนผ่านตารางตัวอ้างอิงโยงตามวิธีที่ Pieter แนะนำในคำตอบของเขา
  2. รักษาความสมบูรณ์ของการอ้างอิงในลักษณะที่ไม่เป็นวงกลม
  3. จัดทำรายการผู้ติดต่อหลักที่สามารถดูแลรักษาได้อย่างดีผ่านIX_AccountsContactsXRef_Primaryดัชนี ดัชนีนี้มีตัวกรองดังนั้นจะใช้งานได้บนแพลตฟอร์มที่รองรับเท่านั้น เนื่องจากดัชนีนี้ถูกระบุด้วยUNIQUEตัวเลือกจึงอาจมีผู้ติดต่อหลักเพียงรายเดียวเท่านั้นสำหรับแต่ละบัญชี

ตัวอย่างเช่นหากคุณต้องการแสดงรายชื่อผู้ติดต่อทั้งหมดพร้อมคอลัมน์แสดงสถานะ "หลัก" แสดงผู้ติดต่อหลักที่ด้านบนของรายการสำหรับแต่ละบัญชีคุณสามารถทำได้:

SELECT A.AccountName
    , C.ContactName
    , XR.IsPrimary
FROM dbo.Accounts A
    INNER JOIN dbo.AccountsContactsXRef XR ON A.AccountID = XR.AccountID
    INNER JOIN dbo.Contacts C ON XR.ContactID = C.ContactID
ORDER BY A.AccountName
    , XR.IsPrimary DESC
    , C.ContactName;

ดัชนีที่กรองจะป้องกันการแทรกที่อยู่ติดต่อหลักมากกว่าหนึ่งรายการต่อบัญชีในขณะเดียวกันก็ให้วิธีการที่รวดเร็วในการส่งคืนรายชื่อผู้ติดต่อหลัก เราสามารถนึกภาพคอลัมน์อื่นได้ง่ายIsActiveด้วยดัชนีที่ไม่ซ้ำกันกรองเพื่อรักษาประวัติผู้ติดต่อต่อบัญชีแม้หลังจากผู้ติดต่อนั้นจะไม่เชื่อมโยงกับบัญชีอีกต่อไป:

ALTER TABLE dbo.AccountsContactsXRef
ADD IsActive BIT NOT NULL
CONSTRAINT DF_AccountsContactsXRef_IsActive
DEFAULT ((1));

CREATE INDEX IX_AccountsContactsXRef_IsActive
ON dbo.AccountsContactsXRef(IsActive)
WHERE IsActive = 1;

1
โดยทั่วไปคุณจะพูดว่าควรหลีกเลี่ยงการอ้างอิงแบบวงกลม ฉันเห็นว่าพวกเขาไม่เลวและใช้มันเพื่อทำให้งานออกแบบมีประสิทธิภาพ พวกเขาทำให้การลบมีความซับซ้อนมากขึ้นเล็กน้อยในสิ่งที่พวกเขาต้องการและอัปเดตเป็น NULL ในเอนทิตีหลักที่จะเป็นอย่างเดียว แต่ฉันพบว่ามันเป็นราคาที่ต่ำในการจ่ายเพื่อความสะดวก ฉันใช้พวกมันใน Postgres โดยที่ฟิลด์ FK เป็นโมฆะดังนั้นฉันจึงสร้างแถวที่มีค่า NULL แล้วอัปเดตเขตข้อมูล FK เป็น PK จากตารางลูกเพื่อทำหน้าที่เดียวกันตามที่อธิบายไว้ใน OP
amphibient

ฉันไม่ชอบการอ้างอิงแบบวงกลมเพียงเพราะพวกเขามักจะทำให้การออกแบบซับซ้อนโดยไม่จำเป็นและเวลาส่วนใหญ่ไม่ได้ให้ประโยชน์ด้านประสิทธิภาพที่สำคัญซึ่งคุ้มค่ากับการแลกเปลี่ยน ฉันเป็นแฟนตัวยงของ Occam's Razor และผลลัพธ์ก็มีแนวโน้มที่จะเป็นทางออกที่ง่ายที่สุดสำหรับปัญหาที่กำหนด
Max Vernon

1
ฉันทั้งหมดเป็นเพราะมีดโกนของอ็อกคัม แต่การออกแบบที่น่าสนใจทำให้ฉันสามารถหลีกเลี่ยงคิวรีที่สองหรือเข้าร่วมได้โดยไม่จำเป็นต้องละเมิดรูปแบบปกติที่ 3 ฉันขอขอบคุณข้อเสนอแนะของคุณ
สะเทินน้ำสะเทินบก

6

ไม่ไม่สามารถยอมรับการอ้างอิงคีย์ต่างประเทศแบบวงกลมได้ ไม่เพียงเพราะมันเป็นไปไม่ได้ที่จะแทรกข้อมูลโดยไม่ลดลงอย่างต่อเนื่องและสร้างข้อ จำกัด แต่เนื่องจากเป็นข้อผิดพลาดพื้นฐานของโดเมนทุกประเภทที่ฉันนึกออก ในตัวอย่างของคุณฉันไม่สามารถนึกถึงโดเมนใด ๆ ที่ความสัมพันธ์ระหว่างบัญชีและผู้ติดต่อไม่ใช่ NN ต้องใช้ตารางแยกที่มีการอ้างอิง FK กลับไปที่ทั้งบัญชีและผู้ติดต่อ

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
)

CREATE TABLE AccountContact
(
    AccountID INT FOREIGN KEY REFERENCES Account(ID),
    ContactID INT FOREIGN KEY REFERENCES Contact(ID),

    primary key(AccountID,ContactID)
)

5
" มันจะเป็นไปไม่ได้ที่จะแทรกข้อมูล " - ไม่มันจะเป็นไปไม่ได้ เพียงแค่ประกาศข้อ จำกัด ที่เลื่อนออกไป แต่ฉันเห็นด้วย: ในเกือบทุกกรณีการอ้างอิงแบบวงกลมเป็นการออกแบบที่ไม่ดี
a_horse_with_no_name

3
@a_horse - ไม่สามารถกำหนดการอ้างอิงที่เลื่อนออกไปได้ใน SQL Server ... ฉันรู้ว่าคุณสามารถใช้งาน Oracle ได้เพียงต้องการชี้ให้เห็นความแตกต่าง
Max Vernon

2
@ MaxVernon: คำถามนี้ไม่เพียง แต่เกี่ยวกับ SQL Server และมี DBMS มากกว่า Oracle ที่สนับสนุนข้อ จำกัด ที่เลื่อนออกไป - แต่อย่างที่ฉันบอกว่า: ฉันเห็นด้วยกับ Pieter ว่าการออกแบบตัวเองผิด (และโซลูชันของเขามีเหตุผลมากกว่า)
a_horse_with_no_name

4
ออกจากกันเฉพาะของตัวอย่างใด ๆ ในข้อตกลงทั่วไปไม่มีอะไรจำเป็นต้องผิดหรือ "ข้อบกพร่อง" เกี่ยวกับการมีการแลกเปลี่ยนซึ่งกันและกัน (เช่น "วงกลม") ข้อ จำกัด การอ้างอิงความสมบูรณ์ นี่มีผลบังคับใช้เป็นเพียงตัวอย่างของการเข้าร่วมการอ้างอิง เข้าร่วมการพึ่งพาเป็นสิ่งที่ดีในหลักการถ้า DBMS ของคุณช่วยให้คุณสามารถใช้พวกเขา เป็นเพียงแค่นั้นใน SQL DBMSs มันไม่ใช่เรื่องง่ายที่จะใช้การพึ่งพาที่ซับซ้อนระหว่างตาราง
nvogel

6
@Pieter, 1-1 ไม่ได้เป็นเพียงตัวอย่างของการพึ่งพาการเข้าร่วมและไม่ใช่กรณีพิเศษโดยเฉพาะ มีหลายกรณีที่ข้อ จำกัด การพึ่งพาการเข้าร่วมทำให้มีเหตุผล
nvogel

1

คุณสามารถให้วัตถุภายนอกชี้ไปยังผู้ติดต่อหลักแทนที่จะไปยังบัญชี ข้อมูลของคุณจะเป็นดังนี้:

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

CREATE TABLE AccountOwner (
    Other Stuff Here . . .
    PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)
)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.