具有嚴格模型的分層引用抽象
我想在嚴格的 DB 模型中抽像出真實世界的層次結構(盡可能嚴格)。
我的問題是要創建一個具有 this-or-this-but-not-both 規則集的結構。
我有兩種實體類型,
- Device,應該可以作為孩子和父母連接到另一個“設備”。
- 組合器,它可以連接多個“設備”作為父母和一個作為孩子。
但是(!)一個設備只能同時連接到其中一個。所以像“設備”一樣只有一個孩子和一個父母。
我嘗試以某種方式將其視覺化:
我所有的想法都不嚴格,但我正在尋找是否有一種模式可以在沒有應用程序級別規則的情況下解決這個問題。在類模型上,我會通過繼承來解決這個問題。一些想法?
這是一個可能的解決方案。從上到下導航結構可能有點粗略,但也許應用程序端的對象可以處理它。
我認為您可以使用 2 個表
dbo.Device
和dbo.Combiner
. 每個表都有一個列,該列引用其父對像是誰,無論它是 adevice
還是 acombiner
。然後,我們可以使用幾種不同的類型constraints
來triggers
確保數據遵循您要使用的路徑。表結構(現在您的對象可能有更多的數據點,而不僅僅是 a
name
,但為了簡單起見,我只會跟踪 aname
)
dbo.Device
CREATE TABLE dbo.Device ( ID INT IDENTITY(1,1) NOT NULL, Name VarChar(100) NOT NULL, ParentDeviceID INT NULL, ParentCombinerID INT NULL, CONSTRAINT [PK_Device] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] ) ON [PRIMARY] CREATE UNIQUE NONCLUSTERED INDEX IX_UniqueParentDeviceIDDevice ON dbo.Device(ParentDeviceID) WHERE ParentDeviceID IS NOT NULL ALTER TABLE [dbo].[Device] WITH CHECK ADD CONSTRAINT [FK_Device_Device] FOREIGN KEY([ParentDeviceID]) REFERENCES [dbo].[Device] ([ID]) GO ALTER TABLE [dbo].[Device] WITH CHECK ADD CONSTRAINT [FK_Combiner_Device] FOREIGN KEY([ParentCombinerID]) REFERENCES [dbo].[Combiner] ([ID]) GO ALTER TABLE [dbo].[Device] ADD CONSTRAINT CHK_Device CHECK (ParentDeviceID IS NULL OR ParentCombinerID IS NULL) GO
dbo.Combiner
CREATE TABLE dbo.Combiner ( ID INT IDENTITY(1,1) NOT NULL, Name VarChar(100) NOT NULL, ParentDeviceID INT NULL, ParentCombinerID INT NULL, CONSTRAINT [PK_Combiner] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] ) ON [PRIMARY] CREATE UNIQUE NONCLUSTERED INDEX IX_UniqueParentDeviceIDCombiner ON dbo.Combiner(ParentDeviceID) WHERE ParentDeviceID IS NOT NULL ALTER TABLE [dbo].[Combiner] WITH CHECK ADD CONSTRAINT [FK_Combiner_Device] FOREIGN KEY([ParentDeviceID]) REFERENCES [dbo].[Device] ([ID]) GO ALTER TABLE [dbo].[Combiner] WITH CHECK ADD CONSTRAINT [FK_Combiner_Combiner] FOREIGN KEY([ParentCombinerID]) REFERENCES [dbo].[Combiner] ([ID]) GO ALTER TABLE [dbo].[Combiner] ADD CONSTRAINT CHK_Combiner CHECK (ParentDeviceID IS NULL OR ParentCombinerID IS NULL) GO
ParentDeviceID
每個表都通過或保存其父對像是誰ParentCombinerID
。Unique Filtered Index
onParentDeviceID
有助於確保給定對Device
像只能是一個對象的父對象(AUnique Constraint
將停止具有NULL
值的多行)。不能保證,這就是為什麼我們將在未來使用一些觸發器。這兩個CHECK Constraint
s 有助於確保任何給定對像只能有 1 個父對象,而不是同時具有 aDevice
和Combiner
作為父對象。Foreign Key
s 確保儲存在ParentDeviceID
和中的ParentCombinerID
值是這兩個表的有效值。
Trigger
s我們可以
Trigger
在每個table
. 這些Trigger
需要確保:
- 在記錄的
insert
/update
上,dbo.Device
儲存的值ParentDeviceID
尚未打開dbo.Combiner.ParentDeviceID
- 在記錄的
Insert
/Update
上,dbo.Combiner
儲存的值ParentDeviceID
尚未打開dbo.Device.ParentDeviceID
-- On the Insert/Update of a dbo.Combiner record a value stored on ParentDeviceID is not already on dbo.Device.ParentDeviceID CREATE TRIGGER dbo.CombinerCheck ON dbo.Combiner AFTER INSERT, UPDATE AS BEGIN IF EXISTS ( SELECT * FROM INSERTED I INNER JOIN Device D ON I.ParentDeviceID = D.ParentDeviceID ) BEGIN RAISERROR ('This Combiner cannot be added since it''s parent Device is already a parent to a different Device.', 16, 1); ROLLBACK TRANSACTION; --stops the Insert/Update RETURN END END --On the insert/update of a dbo.Device record a value stored on ParentDeviceID is not already on dbo.Combiner.ParentDeviceID CREATE TRIGGER dbo.DeviceCheck ON dbo.Device AFTER INSERT, UPDATE AS BEGIN IF EXISTS ( SELECT * FROM INSERTED I INNER JOIN Combiner C ON I.ParentDeviceID = C.ParentDeviceID ) BEGIN RAISERROR ('This Device cannot be added since it''s parent Device is already a parent to a Combiner.', 16, 1); ROLLBACK TRANSACTION; --stops the Insert/Update RETURN END END
作為替代方案,這個 StackOverflow 問題顯然與 a
Unique Index
上的 a給出了相同或相似的效果View
。如果您想避免Triggers
,這可能是您的解決方案。 https://stackoverflow.com/questions/16314372/ms-sql-server-cross-table-constraint。我不熟悉這種方法,但也許它對你來說是一個更好的解決方案。例子
實現提供的第一個範例(鏈中只有 3 個設備)看起來像這樣。中只有 3 條記錄
dbo.Device
:+----+------+----------------+------------------+ | ID | Name | ParentDeviceID | ParentCombinerID | +----+------+----------------+------------------+ | 1 | D1 | NULL | NULL | | 2 | D2 | 1 | NULL | | 3 | D3 | 2 | NULL | +----+------+----------------+------------------+
實現提供的第二個範例(1 個設備 –> 1 個組合器 –> 3 個設備)看起來像這樣。4 條記錄
dbo.Device
和 1 條記錄在dbo.Combiner
.Device +----+------+----------------+------------------+ | ID | Name | ParentDeviceID | ParentCombinerID | +----+------+----------------+------------------+ | 1 | D1 | NULL | NULL | | 2 | D2 | NULL | 1 | | 3 | D3 | NULL | 1 | | 4 | D4 | NULL | 1 | +----+------+----------------+------------------+ Combiner +----+------+----------------+------------------+ | ID | Name | ParentDeviceID | ParentCombinerID | +----+------+----------------+------------------+ | 1 | C1 | 1 | NULL | +----+------+----------------+------------------+
dbo.Device +----+------+----------------+------------------+ | ID | Name | ParentDeviceID | ParentCombinerID | +----+------+----------------+------------------+ | 1 | D1 | NULL | NULL | | 2 | D2 | 1 | NULL | | 3 | D3 | NULL | 1 | | 4 | D4 | NULL | 1 | | 5 | D5 | NULL | 1 | | 6 | D6 | NULL | 2 | | 7 | D7 | NULL | 2 | | 8 | D8 | NULL | 2 | +----+------+----------------+------------------+ dbo.Combiner +----+------+----------------+------------------+ | ID | Name | ParentDeviceID | ParentCombinerID | +----+------+----------------+------------------+ | 1 | C1 | 2 | NULL | | 2 | C2 | NULL | 1 | +----+------+----------------+------------------+
希望這可以為您提供數據庫方面所需的一切。我再次認為這在應用程序端導航表結構會很複雜。但是數據庫應該以您想要的方式保持關係。