在SQL数据库上实施“零或一对一”关系?

时间:2011-11-14 01:39:29

标签: sql-server entity-framework

我有一个Post实体和一个FbPost实体。

Post.FbPost为null或FbPost,并且没有两个Post实体可以引用相同的FbPost实体。换句话说,zero-or-one to one

在SQL Server(理想情况下)中以zero-or-one to one而不是many to one强制执行此操作的正确方法是什么?

如果不可能,我该如何在EF级别强制执行此操作?

4 个答案:

答案 0 :(得分:5)

通常情况下,您可能会尝试将外键(在源表中)置为可空并对其设置唯一约束。

它可以为空的事实意味着您可以在源表中有一个空条目,该条目不引用目标表中的条目。并且,如果它不为null,则唯一约束确保源表中只有一行可以引用目标表中的行。

不幸的是,至少对于SQL Server (a),唯一约束列中的NULL也必须是唯一的,尽管这会“破坏”NULL指令不等于任何值的SQL准则,包括另一个NULL。基本上,这种方法不适用于SQL Server。

出于这种窘境的一种可能方法(b)是具有可空列的外键约束,但是没有唯一约束。这将允许您确保您根本不引用目标表中的行(源表中的NULL)或引用目标行(源表中的任何非NULL值)。

但是,它不会为您提供“只有一个源行可以引用目标行”的要求。这可以添加一个before(插入/更新)触发器,该触发器将检查源表中的每隔一行,以确保没有其他行已经引用目标行。

你应该总是更喜欢数据库本身的约束。你永远不知道恶意应用程序(恶意或错误)何时会连接到你的数据库并决定不遵守规则。


(a)以下文字(here解释)显示了对几个DBMS产品中可为空的唯一列的不同支持:

标准:

如约束名称所示,具有UNIQUE约束的(一组)列可能只包含唯一(组合)值。

受UNIQUE约束约束的列或一组列也必须遵循NOT NULL约束,除非DBMS实现可选的“允许NULL”功能(功能ID 591)。可选功能为UNIQUE约束添加了一些额外的特性:

首先,UNIQUE约束中涉及的列也可能具有NOT NULL约束,但它们不必具有。其次,如果具有UNIQUE约束的列也没有NOT NULL约束,那么列可能包含任意数量的NULL'值'(NULL不等于NULL的逻辑结果)。

<强>的PostgreSQL:

遵循标准,包括可选的NULL允许功能。

<强> DB2:

遵循UNIQUE约束的非可选部分。不实现可选的NULL允许功能。

<强> MSSQL:

扭转标准。

MSSQL提供允许NULL的功能,但如果允许NULL,则允许最多只有一个NULL'value'实例。换句话说,它打破了上述标准描述中的特征2。

<强> MySQL的:

遵循标准,包括可选的NULL允许功能。

<强>甲骨文:

遵循标准,关于多列UNIQUE约束。

实现了可选的NULL允许功能:如果对单个列施加UNIQUE约束,则该列可以包含任意数量的NULL(如上面标准描述中的特征2所预期的那样)。但是,如果为多个列指定了UNIQUE约束,那么Oracle会将约束视为违反任何两行

  • 在受约束
  • 影响的列中包含至少一个NULL
  • 受约束影响的其余列中的相同非NULL值

(b)当然,另一种方法是选择实现此功能的DBMS,如PostgreSQL或MySQL。

在您的特定情况下可能无法实现,但至少应该预期。例如,我避开Oracle,因为它无法从某些字符列中的空字符串中辨别出NULL,虽然其他人不像我那样“纯粹主义”(我的妻子会说“肛门保持”): - )

答案 1 :(得分:4)

创建唯一的过滤索引:

CREATE UNIQUE INDEX Post_Unq_FbPost ON dbo.Post(FbPost) WHERE FbPost IS NOT NULL;

当然也要创建一个外键。

答案 2 :(得分:1)

Superclass It!

我认为最好的解决方案是使Post表成为FBPost表的超类。 (注意:我将在这里使用PostIDFBPostID来清楚地说明我所指的内容。)也就是说,从{{1}中删除FBPostID列完全更新Post列以匹配相应的FBPost.FBPostID后的表格。它不是每个PostID都有自己唯一的值,而是与FBPostID共享相同的值。在我的专业观点中,这是正确的方式来建立一对一或一对一的关系。它具有万无一失的优点,除了简单的FK已经提供的功能外,不需要任何额外的索引,触发器或约束。

注意:我假设一旦我们放弃(假定的)PK,我们就可以更新PostID中的FBPostID列。如果它是FBPost列,则需要更多工作 - 只需添加将成为新PK的新列,并重命名原始列。如果列顺序很重要,则必须将数据移动到新表中,以便在所需位置显示新列。

在处理此问题时,请务必考虑并发性,以便在更改发生时或更改更改的数据处理之前,不会不正确地读取或修改数据。

为什么选择

当你建模identity关系而不是is-a关系,并且这两个实体参与为0到零或一,那么他们应该共享相同的代理键,因为它们实际上是同一个实体(你实际上是在建模has-a关系)。

即使更改为此数据库模型也需要一些工作,但修复次优设计是完全值得的。您的关系数据库的设计应始终利用可用的核心关系功能。你为什么要做其他事情:

  • 您永远不会编写触发器来强制执行外键关系,因为数据库提供了显式外键约束,可以100%可靠地完成工作。
  • 您永远不会直接修改系统表来创建新表,而是发出is-sometimes-a命令。
  • 依此类推。

类似地,您将使用外键/主键组合来建模CREATE TABLE关系,而不必执行奇怪的NULL - 允许唯一约束或过滤索引来实现此目的。

示例更改脚本

这甚至都不太糟糕!这里有一些脚本可以让你很好地开始:

is-sometimes-a

最后,修改您的BEGIN TRAN; ALTER TABLE dbo.Post DROP CONSTRAINT FK_Post_FBPostID; -- referencing FBPost -- Also remove all FKs from any other tables referencing FBPostID ALTER TABLE dbo.FBPost DROP CONSTRAINT PK_FBPost; -- FBPostID column not PK ALTER TABLE dbo.FBPost ADD OriginalFBPostID int; UPDATE dbo.FBPost WITH (HOLDLOCK, UPDLOCK) SET OriginalFBPostID = FBPostID; UPDATE F SET F.FBPostID = P.PostID FROM dbo.FBPost F INNER JOIN dbo.Post P ON F.OriginalFBPostID = P.FBPostID; -- Perform a similar update on all other tables referencing FBPostID -- Now, the two most important changes ALTER TABLE dbo.FBPost ADD CONSTRAINT PK_FBPost PRIMARY KEY CLUSTERED (FBPostID); ALTER TABLE dbo.FBPost ADD CONSTRAINT FK_FBPost_PostID FOREIGN KEY REFERENCES dbo.Post (PostID); -- This is where the magic happens! ALTER TABLE dbo.SomeTable ADD CONSTRAINT FK_SomeTable_FBPostID FOREIGN KEY REFERENCES dbo.FBPost (FBPostID); -- and all other tables referencing FBPostID EXEC sp_rename 'dbo.Post.FBPostID', 'OriginalFBPostID'; -- should stop using it ALTER TABLE dbo.Post DROP COLUMN FBPostID; -- Or even better, remove it. COMMIT TRAN; ALTER TABLE dbo.FBPost DROP COLUMN OriginalFBPostID; -- Meaningless now -- If you keep OriginalFBPostID and it is identity, please copy the values -- to a new non-identity column and drop it so you don't keep generating more / Post插入代码,将FBPost用作PostID

新查询外观

只是为了推动这一点,你之间的联接看起来像这样:

FBPostID

但现在看起来像这样:

SELECT
   P.Something,
   F.SomethingElse
FROM
   dbo.Post P
   INNER JOIN dbo.FBPost F
      ON P.FBPostID = F.FBPostID

现在问题彻底解决了!您的表格占用的空间更少(从SELECT P.Something, F.SomethingElse FROM dbo.Post P INNER JOIN dbo.FBPost F ON P.PostID = F.FBPostID -- the important part 丢失FBPostID列)。您不必使用允许多个NULL的FK。在Post表格中FBPostID上的PK显示,每FBPost行只能有一个FBPost行。

答案 3 :(得分:0)

NULL能为你提供0 ... FK参考上的UNIQUE CONSTRAINT将为您提供1。