2013-08-26 84 views
82

我有一个保存数据的表,其中一个行需要存在于另一个表中。所以,我想要一个外键来保持参照完整性。非主键的外键

CREATE TABLE table1 
(
    ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY, 
    AnotherID INT NOT NULL, 
    SomeData VARCHAR(100) NOT NULL 
) 

CREATE TABLE table2 
(
    ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY, 
    AnotherID INT NOT NULL, 
    MoreData VARCHAR(30) NOT NULL, 

    CONSTRAINT fk_table2_table1 FOREIGN KEY (AnotherID) REFERENCES table1 (AnotherID) 
) 

但是,正如你所看到的,表I的外键,列不是PK。有没有办法创建这个外键,或者更好的方法来维护这个参照完整性?

+0

这样做没什么意义。为什么不引用'table1.ID'? – zerkms

+0

这是明确的,如果你的AnothidID不是主键它应该是一个ForeignKey,所以作为一个ForeignKey,你的table2应该指向同一个表(可能的table3) –

回答

107

如果你真的想创建一个非主键的外键,它必须是一个有唯一约束的列。

Books Online

一个外键约束不必只链接到另一个表中的PRIMARY KEY 约束;它也可以定义为在另一个表中引用 UNIQUE约束的列。

所以在你的情况下,如果你让AnotherID是唯一的,它将被允许。如果你不能应用独特的约束条件,那么你的运气不好,但如果你仔细想想,这确实是有道理的。

虽然如前所述,如果你有一个非常好的主键作为候选键,为什么不使用它呢?

+1

与你最后一个问题相关...我有一种情况我希望复合候选键是主键**,因为它在语义上具有更重要的意义,并且最好地描述了我的模型。为了表现(如上所述),我也希望有一个外键引用一个新创建的代理键。有没有人预见到这样的设置有任何问题? –

+0

先生你能告诉一下这个外键背后的逻辑是否总是引用具有唯一约束的属性? –

+0

如何在asp.net中做到这一点MVC 5 – lostmylogin

10

正如其他人指出的,理想情况下,外键将被创建为对主键(通常是IDENTITY列)的引用。然而,我们并不生活在理想的世界中,有时候甚至是对模式的“小”改变都会对应用程序逻辑产生重大的连锁反应。

考虑具有SSN列(和哑主键)的Customer表以及还包含SSN列(由客户数据中的业务逻辑填充,但不存在FK)的Claim表。这个设计是有缺陷的,但已经使用了好几年,并且在这个模式上已经建立了三种不同的应用程序。很明显,剥离Claim.SSN并建立一个真正的PK-FK关系将是理想的,但也可能是一次重大的检修。另一方面,对Customer.SSN设置UNIQUE约束,并在Claim.SSN上添加FK,可以提供参考完整性,对应用程序几乎没有影响。

不要误解我的意思,我只是为了规范化,但有时候实用主义胜过理想主义。如果一个平庸的设计可以帮助创建一个创可贴,那么可以避免手术。

8

Necromancing。
我假设有人在这里登陆时,他需要一个外键列入包含非唯一键的表中。

问题是,如果你有这个问题,数据库模式是非规范化的。

例如,您将一个房间保存在一个表格中,其中包含room-uid主键,DateFrom和DateTo字段,以及另一个uid,这里是RM_ApertureID以跟踪同一个房间,以及软删除字段,如RM_Status,其中99表示“已删除”,而<> 99表示“有效”。

因此,当您创建第一个房间时,将RM_UID和RM_ApertureID作为与RM_UID相同的值插入。 然后,当您将房间终止到某个日期并重新建立新日期范围时,RM_UID为newid(),并且来自先前条目的RM_ApertureID变为新的RM_ApertureID。

所以,如果是这样的话,RM_ApertureID是一个非唯一的字段,所以你不能在另一个表中设置一个外键。

而且没有办法将外键设置为非唯一的列/索引,例如,在T_ZO_REM_AP_Raum_Reinigung(WHERE RM_UID实际上是RM_ApertureID)。
但禁止无效的值,你需要设置一个外键,否则,数据垃圾是结果宜早不宜迟...

现在你可以在这种情况下做什么(短rewritting整个应用程序的)插入一个CHECK约束,用标量函数检查密钥是否存在:

IF EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]')) 
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung DROP CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
GO 


IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_Constaint_ValidRmApertureId]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) 
DROP FUNCTION [dbo].[fu_Constaint_ValidRmApertureId] 
GO 




CREATE FUNCTION [dbo].[fu_Constaint_ValidRmApertureId](
    @in_RM_ApertureID uniqueidentifier 
    ,@in_DatumVon AS datetime 
    ,@in_DatumBis AS datetime 
    ,@in_Status AS integer 
) 
    RETURNS bit 
AS 
BEGIN 
    DECLARE @bNoCheckForThisCustomer AS bit 
    DECLARE @bIsInvalidValue AS bit 
    SET @bNoCheckForThisCustomer = 'false' 
    SET @bIsInvalidValue = 'false' 

    IF @in_Status = 99 
     RETURN 'false' 


    IF @in_DatumVon > @in_DatumBis 
    BEGIN 
     RETURN 'true' 
    END 


    IF @bNoCheckForThisCustomer = 'true' 
     RETURN @bIsInvalidValue 


    IF NOT EXISTS 
    ( 
     SELECT 
      T_Raum.RM_UID 
      ,T_Raum.RM_Status 
      ,T_Raum.RM_DatumVon 
      ,T_Raum.RM_DatumBis 
      ,T_Raum.RM_ApertureID 
     FROM T_Raum 
     WHERE (1=1) 
     AND T_Raum.RM_ApertureID = @in_RM_ApertureID 
     AND @in_DatumVon >= T_Raum.RM_DatumVon 
     AND @in_DatumBis <= T_Raum.RM_DatumBis 
     AND T_Raum.RM_Status <> 99 
    ) 
     SET @bIsInvalidValue = 'true' -- IF ! 

    RETURN @bIsInvalidValue 
END 



GO 



IF EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]')) 
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung DROP CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
GO 


-- ALTER TABLE dbo.T_AP_Kontakte WITH CHECK ADD CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung WITH NOCHECK ADD CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
CHECK 
( 
    NOT 
    ( 
     dbo.fu_Constaint_ValidRmApertureId(ZO_RMREM_RM_UID, ZO_RMREM_GueltigVon, ZO_RMREM_GueltigBis, ZO_RMREM_Status) = 1 
    ) 
) 
GO 


IF EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung]') AND parent_object_id = OBJECT_ID(N'[dbo].[T_ZO_REM_AP_Raum_Reinigung]')) 
ALTER TABLE dbo.T_ZO_REM_AP_Raum_Reinigung CHECK CONSTRAINT [Check_RM_ApertureIDisValid_T_ZO_REM_AP_Raum_Reinigung] 
GO 
+0

总是迟到派对......但是,感谢这个真实世界的建议 - 我确切地说 - 次表中的数据是版本化的(除了一个键以外还有一个日期范围),我只想链接我的主表中的最新版本... – Ian