由于您正在使用可为空的字段作为外键,因此实际上您可以构建一个按照您设想的方式正确工作的系统。为了将行插入到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;
这提供了以下功能:
- 通过交叉引用表清楚地描述联系人和客户之间的关系,就像彼得在他的答案中所建议的那样
- 以健全,非圆形的方式维护参照完整性。
- 通过索引提供高度可维护的主要联系人列表
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;