可以使用循环外键引用\如何避免使用它们?


29

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

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

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

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

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,您
很不

Answers:


12

由于您正在使用可为空的字段作为外键,因此实际上您可以构建一个按照您设想的方式正确工作的系统。为了将行插入到Accounts表中,您需要在Contacts表中存在一行,除非您允许插入具有空PrimaryContactID的Accounts。为了在不存在“客户”行的情况下创建联系人行,必须允许“联系人”表中的“客户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. 通过交叉引用表清楚地描述联系人和客户之间的关系,就像彼得在他的答案中所建议的那样
  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字段可为空,因此我用FNULL字段创建了一行,然后将FK字段从子表更新为PK,几乎完成了与OP中所述相同的功能
两栖类,

我不喜欢循环引用,只是因为循环引用会不必要地使设计复杂化,而且在大多数情况下,它们并没有提供任何值得权衡的明显性能优势。我是Occam的Razor的粉丝,因此倾向于解决给定问题的最简单解决方案。
Max Vernon

1
我全都喜欢Occam的剃须刀,但是上述设计使我能够避免一些第二查询或联接,而不必违反第三范式。多谢您的
宝贵

6

不,拥有循环外键引用是不可接受的。不仅因为无法不删除和重新创建约束就不可能插入数据。但因为它是我能想到的任何领域的根本缺陷模型。在您的示例中,我无法想到其中Account和Contact之间的关系不是NN的任何域,它需要一个联结表,其中FK引用又返回到Account和Contact。

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 2015年

3
@a_horse-无法在SQL Server中定义可延迟的引用...我知道您可以在Oracle中,只是想指出差异。
Max Vernon

2
@MaxVernon:这个问题不仅是关于SQL Server的问题,还有比支持可延迟约束的Oracle更多的DBMS,而不仅仅是Oracle-但正如我所说:我确实同意Pieter的观点,即设计本身是错误的(而且他的解决方案更有意义)
a_horse_with_no_name15年

4
撇开任何一个示例的细节,总的来说,关于相互(即“圆形”)参照完整性约束没有必然是错误的或“有缺陷的”。实际上,这只是连接依赖项的一个示例。如果您的DBMS允许您实现联接依赖关系,则原则上是一件好事。只是在SQL DBMS中,在表之间实现复杂的依赖关系并不容易。
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)
)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.