介绍超类型和子类型
我建议您使用超类型和子类型。首先,创建PartyType 和Party 表:
CREATE TABLE dbo.PartyType (
PartyTypeID int NOT NULL identity(1,1) CONSTRAINT PK_PartyType PRIMARY KEY CLUSTERED
Name varchar(32) CONSTRAINT UQ_PartyType_Name UNIQUE
);
INSERT dbo.PartyType VALUES ('Person'), ('Business');
超类型
CREATE TABLE dbo.Party (
PartyID int identity(1,1) NOT NULL CONSTRAINT PK_Party PRIMARY KEY CLUSTERED,
FullName varchar(64) NOT NULL,
BeginDate smalldatetime, -- DOB for people or creation date for business
PartyTypeID int NOT NULL
CONSTRAINT FK_Party_PartyTypeID FOREIGN KEY REFERENCES dbo.PartyType (PartyTypeID)
);
子类型
然后,如果有一个 Person 唯一的列,则创建一个 Person 表,其中仅包含这些列:
CREATE TABLE dbo.Person (
PersonPartyID int NOT NULL
CONSTRAINT PK_Person PRIMARY KEY CLUSTERED
CONSTRAINT FK_Person_PersonPartyID FOREIGN KEY REFERENCES dbo.Party (PartyID)
ON DELETE CASCADE,
-- add columns unique to people
);
如果存在企业独有的列,请创建一个仅包含这些列的 Business 表:
CREATE TABLE dbo.Business (
BusinessPartyID int NOT NULL
CONSTRAINT PK_Business PRIMARY KEY CLUSTERED
CONSTRAINT FK_Business_BusinessPartyID FOREIGN KEY REFERENCES dbo.Party (PartyID)
ON DELETE CASCADE,
-- add columns unique to businesses
);
使用和注意事项
最后,您的Asset 表将如下所示:
CREATE TABLE dbo.Asset (
AssetID int NOT NULL identity(1,1) CONSTRAINT PK_Asset PRIMARY KEY CLUSTERED,
PartyID int NOT NULL
CONSTRAINT FK_Asset_PartyID FOREIGN KEY REFERENCES dbo.Party (PartyID),
AssetTag varchar(64) CONSTRAINT UQ_Asset_AssetTag UNIQUE
);
超类型 Party 表与子类型表 Business 和 Person 共享的关系是“一对零或一”。现在,虽然子类型通常在另一个表中没有对应的行,但在这种设计中,有可能在两个表中都有一个 Party。但是,您实际上可能喜欢这样:有时一个人和一个企业几乎可以互换。如果没有用,虽然执行此操作的触发器很容易完成,但最好的解决方案可能是将 PartyTypeID 列添加到子类型表中,使其成为 PK 和 FK 的一部分,并在 @ 上放置一个 CHECK 约束987654337@.
此模型的美妙之处在于,当您要创建一个对企业或个人有约束的列时,您可以对适当的表而不是聚会表进行约束。
此外,如果需要,在约束上打开级联删除可能很有用,以及在子类型表上使用 INSTEAD OF DELETE 触发器来代替从超类型表中删除相应的 ID(这保证没有超类型行没有存在子类型行)。这些查询非常简单,可以在整个行存在或不存在的级别上工作,在我看来,这对任何需要检查列值一致性的设计来说都是一个巨大的改进。
另外,请注意,在许多情况下,您认为应该放在其中一个子类型表中的列实际上可以组合到超类型表中,例如社会保险号。将其称为 TIN(纳税人识别号),它适用于企业和个人。
ID 列命名
是否将Person表中的列称为PartyID、PersonID或PersonPartyID的问题是您自己的喜好,但我认为最好将这些称为PersonPartyID或BusinessPartyID——容忍较长名称的成本,这避免了两种类型的混淆。例如,不熟悉数据库的人看到BusinessID 并且不知道这是PartyID,或者看到PartyID 并且不知道它被外键限制为仅限Business 表中的那些。
如果您想为Party 和Business 表创建视图,它们甚至可以是物化视图,因为它是一个简单的内部连接,您可以重命名PersonPartyID 列PersonID 如果你真的这么想的话(虽然我不会)。如果它对您很有价值,您甚至可以在这些视图上创建INSTEAD OF INSERT 和INSTEAD OF UPDATE 触发器来为您处理对这两个表的插入,使视图在许多应用程序中看起来完全像它们自己的表。
使您提议的设计按原样工作
另外,我不想提它,但如果你想在你提议的设计中设置一个约束,强制只填写一列,下面是代码:
ALTER TABLE dbo.Assets
ADD CONSTRAINT CK_Asset_PersonOrBusiness CHECK (
CASE WHEN PersonID IS NULL THEN 0 ELSE 1 END
+ CASE WHEN BusinessID IS NULL THEN 0 ELSE 1 END = 1
);
但是,我不推荐这种解决方案。
最后的想法
要添加的第三个自然子类型是组织,即人们和企业可以拥有成员资格。超类型和子类型还可以优雅地解决客户/员工、客户/供应商以及与您提出的问题类似的其他问题。
注意不要将“Is-A”与“Acts-As-A”混淆。您可以通过查看您的订单表或查看订单计数来判断一方是客户,并且可能根本不需要客户表。也不要将身份与生命周期混淆:租赁的汽车最终可能会被出售,但这是生命周期的进展,应该使用列数据而不是表格存在来处理——汽车不是以 @987654355 开始的@ 然后变成ForSaleCar,它一直都是汽车。或者RentalItem,也许企业也会租用其他东西。你明白了。
甚至可能不需要PartyType 表。派对类型可以通过相应子类型表中是否存在一行来确定。这也将避免PartyTypeID 与子类型表存在不匹配的潜在问题。一种可能的实现是保留PartyType 表,但从Party 表中删除PartyTypeID,然后在Party 表的视图中根据哪个子类型表具有相应的行返回正确的PartyTypeID。如果您选择允许双方同时成为两个子类型,这将不起作用。然后你会坚持使用子类型视图并知道BusinessID 和PersonID 的相同值指的是同一方。
进一步阅读此模式
请参阅A Universal Person and Organization Data Model 以获得更完整的理论处理。
我最近发现以下文章对于描述在数据库中建模继承的一些替代方法很有用。尽管特定于 Microsoft 的实体框架 ORM 工具,但您没有理由不能在任何数据库开发中自己实现这些:
附:由于我有更多的经验,我不止一次地改变了我对子类型表中 ID 的列命名的看法。