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