1部分:编码Algrebraic数据类型在关系表
我挣扎着这事很多次。我终于发现了在关系表中建模代数数据类型的关键:Check constraints。
使用检查约束,您可以为多态类型的所有成员使用公共表,但仍然强制实施每个成员的不变量。
考虑下面的SQL架构:
CREATE TABLE ConcreteType (
Id TINYINT NOT NULL PRIMARY KEY,
Type VARCHAR(10) NOT NULL
)
INSERT ConcreteType
VALUES
(1,'Concrete1'),
(2,'Concrete2')
CREATE TABLE Base (
Id INT NOT NULL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
ConcreteTypeId TINYINT NOT NULL,
BaseReferenceId INT NULL)
GO
ALTER TABLE Base
ADD CONSTRAINT FK_Base_ConcreteType
FOREIGN KEY(ConcreteTypeId)
REFERENCES ConcreteType(Id)
ALTER TABLE Base
ADD CONSTRAINT FK_Base_BaseReference
FOREIGN KEY(BaseReferenceId)
REFERENCES Base(Id)
简单,对不对?
我们已经通过消除该表格解决了代表抽象基类的表中存在无意义数据的关注点#1。我们还组合了用于独立建模每个具体类型的表,而不是将它们的所有Base
实例存储在同一个表中,而不管它们的具体类型如何。
按原样,此架构不会限制您的Base
类型的多态性。原样,可以插入行ConcreteType1
与非空BaseReferenceId
或行ConcereteType2
与空BaseReferenceId
。 没有什么能够阻止你插入无效数据,所以你需要非常勤奋的插入和编辑。
这是检查约束真正发挥的地方。
ALTER TABLE Base
ADD CONSTRAINT Base_Enforce_SumType_Properties
CHECK
(
(ConcreteTypeId = 1 AND BaseReferenceId IS NULL)
OR
(ConcreteTypeId = 2 AND BaseReferenceId IS NOT NULL)
)
检查约束Base_Enforce_SumType_Properties
定义每个具体类型不变,保护在插入和更新数据。继续运行所有的DDL,在您自己的数据库中创建ConcreteType
和Base
表。然后尝试将行插入Base
,这些行破坏了检查约束中描述的规则。你不能!最后,你的数据模型保持在一起。
为了解决问题#2:现在您的类型的所有成员都在单个表中(使用不变式实施),您的查询将会更简单。你甚至不需要“等同于SQL中的match
F#关键字”。添加新的具体类型非常简单,只需在ConcreteType
表中插入新行,将任何新属性添加为表Base
中的列,然后修改约束以反映任何新的不变量。
2部分:分层编码(读:递归),在SQL Server关系
关注#2部分,我认为关于跨ConcreteType2
和Base
之间存在的“父子”关系查询的复杂性。有很多方法可以处理这种查询并选择一种,我们需要考虑一个特定的用例。
示例用例:我们希望查询每个单个的Base
实例并组合包含每一行的对象图。这很容易;我们甚至不需要加入。我们只需要一个可变的Dictionary<int,Base>
和Id
作为钥匙。
这里有很多需要考虑的东西:有一个名为HierarchyID
(docs)的MSSQL数据类型,它实现了'物化路径'模式,可以更容易地建模类似于你的层次结构。您可以在Base.ID
/Base.BaseReferenceID
列中尝试使用HierarchyID
而不是INT
。
我希望这会有所帮助。
在我看来,你的类型Base大致相当于(Id,Name)元组的非空列表?是这样吗?还是你调整了这个例子来问这个问题? –
@RobertNielsen:起初,我修改了这个例子以问一个问题,但现在我想我会像这样(一个(Id,Type)列表),我将创建另一个表来包含具体类型的公共信息,一个基础信息表,每一个具体的表将用一个外键引用。我这样做是因为否则基表将在行之间包含相同的信息。 – Mario