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

如果不能,如何避免这些情况?

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

下面是一个循环引用(在我看来)在哪里可以接受的示例: >
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)


评论

“如果是这样,如何插入数据”-取决于所使用的DBMS。例如,Postgres,Oracle,SQLite和Apache Derby允许推迟的约束,这将使之成为可能。对于其他DBMS,您很不走运(但是我仍然会首先质疑是否需要这种约束)

#1 楼

由于您正在使用可为空的字段作为外键,因此实际上您可以构建一个可以按照您预期的方式正常工作的系统。为了将行插入到Accounts表中,除非您允许使用空PrimaryContactID插入到Accounts中,否则在Contacts表中必须有一行。为了在不存在“客户”行的情况下创建联系人行,必须允许“联系人”表中的“客户ID”列为空。这允许帐户没有联系人,并且允许联系人没有帐户。也许这是可取的,也许不是。

话虽如此,我个人的喜好是进行以下设置:

通过交叉引用表清楚地描述联系人和客户之间的关系,就像彼得在他的回答中所建议的那样。
以健全,非圆形的方式保持引用完整性。
提供通过IX_AccountsContactsXRef_Primary索引获取高度可维护的主要联系人列表。该索引包含一个过滤器,因此它将仅在支持它们的平台上工作。由于此索引是使用UNIQUE选项指定的,因此每个帐户只能有一个主要联系人。例如,如果要显示所有联系人的列表,其中一列表示“主要”状态,可以在每个帐户的列表顶部显示主要联系人,您可以执行以下操作:

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;


筛选出的索引可以防止插入多个主要联系人每个帐户的联系方式,同时提供一种返回主要联系人列表的快速方法。可以轻松想象另一列IsActive,该列具有非唯一的过滤索引来维护每个帐户的联系人历史记录,即使该联系人不再与该帐户关联也是如此:

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;


#2 楼

不,具有循环外键引用是不可接受的。不仅因为在不不断删除和重新创建约束的情况下插入数据是不可能的。但因为它是我能想到的任何领域的根本缺陷模型。在您的示例中,我无法想到在任何领域中,帐户和联系人之间的关系都不是N-N,因此需要一个联结表,其中FK引用要同时返回到帐户和联系人。

评论


“不可能插入数据”-不,这并非不可能。只需将约束声明为可延迟即可。但我确实同意:在几乎所有情况下,循环引用都是一个不好的设计。

– a_horse_with_no_name
15年5月31日在20:44



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

– Max Vernon♦
2015年6月1日于1:07

#3 楼

您可以使外部对象指向主要联系人,而不是客户。您的数据如下所示:

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)
)