Có thể chấp nhận được các tham chiếu khóa ngoài vòng tròn \ Làm thế nào để tránh chúng?


29

Có thể chấp nhận tham chiếu vòng tròn giữa hai bảng trên trường khóa ngoài không?

Nếu không, làm thế nào những tình huống này có thể tránh được?

Nếu vậy, làm thế nào dữ liệu có thể được chèn?

Dưới đây là một ví dụ về nơi (theo ý kiến ​​của tôi) một tham chiếu vòng tròn sẽ được chấp nhận:

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
" Nếu vậy, làm thế nào dữ liệu có thể được chèn " - phụ thuộc vào DBMS đang được sử dụng. Ví dụ, Postgres, Oracle, SQLite và Apache Derby cho phép các ràng buộc có thể khắc phục được sẽ làm cho điều này trở nên khả thi. Với các DBMS khác, bạn không gặp may (Nhưng tôi vẫn sẽ tranh chấp sự cần thiết của một ràng buộc như vậy ngay từ đầu)
a_horse_with_no_name

Câu trả lời:


12

Vì bạn đang sử dụng các trường nullable cho các khóa ngoại, trên thực tế, bạn có thể xây dựng một hệ thống hoạt động chính xác theo cách bạn hình dung nó. Để chèn các hàng vào bảng Tài khoản, bạn cần phải có một hàng trong bảng Danh bạ trừ khi bạn cho phép chèn vào Tài khoản có null PrimaryContactID. Để tạo một hàng liên hệ mà chưa có hàng Tài khoản, bạn phải cho phép cột AccountID trong bảng Danh bạ là không thể. Điều này cho phép Tài khoản không có liên hệ và cho phép Danh bạ không có tài khoản. Có lẽ đây là mong muốn, có lẽ không.

Có nói rằng, sở thích cá nhân của tôi sẽ có các thiết lập sau:

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;

Điều này cung cấp khả năng:

  1. Phân định rõ ràng mối quan hệ giữa các liên hệ và tài khoản thông qua bảng tham chiếu chéo theo cách mà Pieter đề xuất trong câu trả lời của mình
  2. Duy trì tính toàn vẹn tham chiếu một cách hợp lý, không tuần hoàn.
  3. Cung cấp cho một danh sách duy trì cao các liên hệ chính thông qua IX_AccountsContactsXRef_Primarychỉ mục. Chỉ mục này chứa bộ lọc, vì vậy nó sẽ chỉ hoạt động trên các nền tảng hỗ trợ chúng. Vì chỉ mục này được chỉ định với UNIQUEtùy chọn, nên chỉ có thể có một liên hệ chính duy nhất cho mỗi tài khoản.

Ví dụ: nếu bạn muốn hiển thị danh sách tất cả các liên hệ, với một cột biểu thị trạng thái "chính", hiển thị các liên hệ chính ở đầu danh sách cho mỗi Tài khoản, bạn có thể làm:

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;

Chỉ mục được lọc ngăn không cho chèn nhiều hơn một liên hệ chính trên mỗi tài khoản, đồng thời cung cấp phương thức nhanh chóng trả về danh sách các liên hệ chính. Người ta có thể dễ dàng tưởng tượng một cột khác, IsActivevới chỉ mục được lọc không duy nhất để duy trì lịch sử liên hệ trên mỗi tài khoản, ngay cả sau khi liên hệ đó không còn được liên kết với tài khoản:

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
Nói chung, bạn nên nói rằng các tham chiếu vòng tròn nên tránh? Tôi cho rằng chúng không tệ và đã sử dụng chúng để hoàn thành các thiết kế hiệu quả. Họ thực hiện xóa một chút phức tạp hơn ở chỗ họ yêu cầu và cập nhật lên NULL trong thực thể cha mẹ khác nhưng tôi thấy đó là một mức giá thấp để trả cho sự thuận tiện. Tôi sử dụng chúng trong Postgres, trong đó trường FK là null vì vậy tôi tạo một hàng với nó NULL và sau đó cập nhật trường FK lên PK từ bảng con để thực hiện khá nhiều chức năng tương tự như được mô tả trong OP
amphibient

Tôi không thích các tài liệu tham khảo vòng tròn đơn giản vì chúng có xu hướng phức tạp hóa thiết kế và hầu hết thời gian không mang lại bất kỳ lợi ích hiệu suất đáng kể nào đáng để đánh đổi. Tôi là một fan hâm mộ của Occam's Razor, và kết quả là hướng đến giải pháp đơn giản nhất cho một vấn đề nhất định.
Max Vernon

1
Tôi hoàn toàn dành cho dao cạo của Occam nhưng thiết kế đã nêu ở trên cho phép tôi tránh một số truy vấn hoặc tham gia thứ 2 trong khi không nhất thiết phải vi phạm mẫu thứ 3 thông thường. Tôi đánh giá cao phản hồi của bạn
lưỡng cư

6

Không, không thể chấp nhận được các tham chiếu khóa ngoài vòng tròn. Không chỉ bởi vì sẽ không thể chèn dữ liệu mà không liên tục làm rơi và tạo lại các ràng buộc. nhưng bởi vì nó là một mô hình thiếu sót cơ bản của bất kỳ và mọi lĩnh vực tôi có thể nghĩ đến. Trong ví dụ của bạn, tôi không thể nghĩ ra bất kỳ miền nào trong đó mối quan hệ giữa Tài khoản và Liên hệ không phải là NN, yêu cầu bảng nối với các tham chiếu FK trở lại cho cả Tài khoản và Liên hệ.

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
" Sẽ không thể chèn dữ liệu " - không, nó sẽ không thể. Chỉ cần khai báo các ràng buộc là có thể bảo vệ. Nhưng tôi đồng ý: trong hầu hết các trường hợp, tài liệu tham khảo tròn là một thiết kế tồi.
a_horse_with_no_name

3
@a_horse - không thể xác định tham chiếu có thể xóa được trong SQL Server ... Tôi biết bạn có thể trong Oracle, chỉ muốn chỉ ra sự khác biệt.
Max Vernon

2
@MaxVernon: câu hỏi không chỉ về SQL Server và có nhiều DBMS hơn là Oracle hỗ trợ các ràng buộc có thể bảo vệ - nhưng như tôi đã nói: Tôi đồng ý với Pieter rằng bản thân thiết kế đã sai (và giải pháp của anh ấy có ý nghĩa hơn nhiều)
a_horse_with_no_name

4
Bỏ qua các chi tiết cụ thể của bất kỳ một ví dụ nào, về mặt khái quát, không có gì nhất thiết sai hoặc "thiếu sót" về việc có các ràng buộc toàn vẹn tham chiếu (tức là "thông tư"). Đây chỉ là một ví dụ về Phụ thuộc Tham gia. Tham gia phụ thuộc là một điều tốt về nguyên tắc nếu DBMS của bạn cho phép bạn thực hiện chúng. Chỉ là trong các DBMS SQL, việc thực hiện các phụ thuộc phức tạp giữa các bảng là không dễ dàng.
nvogel

6
@Pieter, 1-1 không phải là ví dụ duy nhất về sự phụ thuộc tham gia và thậm chí nó không phải là trường hợp đặc biệt. Có những trường hợp tham gia ràng buộc phụ thuộc có ý nghĩa hoàn hảo.
nvogel

1

Bạn có thể có đối tượng bên ngoài trỏ đến liên hệ chính, thay vì vào tài khoản. Dữ liệu của bạn sẽ trông như thế này:

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)
)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.