循环外键引用是否可以接受\如何避免它们?

Kid*_*ode 38 rdbms foreign-key database-design

在外键字段上的两个表之间进行循环引用是否可以接受?

如果没有,如何避免这些情况?

如果是这样,如何插入数据?

以下是(在我看来)可以接受循环引用的示例:

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)
Run Code Online (Sandbox Code Playgroud)

Han*_*non 19

由于您将可空字段用于外键,因此您实际上可以构建一个按照您设想的方式正常工作的系统。为了将行插入到 Accounts 表中,您需要在 Contacts 表中有一行,除非您允许插入到带有 null PrimaryContactID 的 Accounts 中。为了在没有 Account 行的情况下创建联系人行,您必须允许 Contacts 表中的 AccountID 列可以为 null。这允许 Accounts 没有联系人,并且允许 Contacts 没有帐户。也许这是可取的,也许不是。

话虽如此,我个人的偏好是具有以下设置:

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;
Run Code Online (Sandbox Code Playgroud)

这提供了以下能力:

  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;
Run Code Online (Sandbox Code Playgroud)

过滤索引可防止每个帐户插入多个主要联系人,同时提供返回主要联系人列表的快速方法。可以很容易地想象另一列,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;
Run Code Online (Sandbox Code Playgroud)


Pie*_*ens 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)
)
Run Code Online (Sandbox Code Playgroud)

  • “*插入数据是不可能的*” - 不,这不是不可能的。只需将约束声明为可延迟。但我确实同意:在几乎所有情况下,循环引用都是一个糟糕的设计。 (10认同)
  • @Pieter,1-1 不是连接依赖的唯一例子,它甚至不是一个特别的例子。在某些情况下,连接依赖约束非常有意义。 (6认同)
  • 撇开任何一个例子的细节不谈,一般来说,具有互惠(即“循环”)参照完整性约束并没有什么必然的错误或“缺陷”。这实际上只是 Join Dependency 的一个示例。如果您的 DBMS 允许您实现它们,原则上加入依赖项是一件好事。只是在 SQL DBMS 中,实现表之间复杂的依赖关系并不容易。 (5认同)
  • @sqlvogel:我是数据完整性约束的布道者;但是如果两个实体被约束为 1-1 对应,则循环 FK 是强制执行该约束的错误方法。 (2认同)