非主键的外键


136

我有一个保存数据的表,并且这些行之一需要存在于另一个表中。因此,我想要一个外键来保持引用完整性。

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)
)

但是,如您所见,我的外键表所在的列不是PK。有没有一种方法可以创建此外键,或者有更好的方法来维护此引用完整性?


这样做没有多大意义。为什么不参考table1.ID
zerkms 2013年

可以肯定的是,如果您的AnothidID不是主键,那么它应该是外键,因此作为外键,您的table2应该指向同一张表(可能是table3)
Roger Barreto 2013年

Answers:


182

如果您确实要为非主键创建外键,则它必须是对其具有唯一约束的列。

在线书籍

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

因此,在您的情况下,如果您使AnotherID唯一,它将被允许。如果您不能应用唯一约束,那么您就不走运了,但是如果您考虑一下,这确实很有意义。

尽管如上所述,如果您有一个很好的主键作为候选键,为什么不使用它呢?


1
与您的最后一个问题有关...我遇到一种情况,我希望复合候选键成为主键因为它在语义上更重要,并且可以最好地描述我的模型。为了性能起见,我也希望外键引用一个新创建的代理键(如上所述)。有没有人预见到这种设置会有任何问题?
丹尼尔·马西亚斯

先生,您能告诉我外键始终引用具有唯一约束的属性的逻辑是什么吗?
Shivangi Gupta

如何在ASP Net MVC 5中做到这一点
irfandar

可以在其他表中将普通的非主键整数声明为外键吗?像这个。这可能吗?创建表项目(PSLNO数值(8,0)不为空,PrMan数值(8,0),字符串数值(8,0),CONSTRAINT PK_Project主键(PSLNO),CONSTRAINT FK_Project1外键(PrMan)参考雇员(EmpID) ,CONSTRAINT FK_Project2外键(StEng)参考Employee(EmpID),)
Nabid

19

正如其他人指出的那样,理想情况下,将创建外键作为对主键的引用(通常是IDENTITY列)。但是,我们并不生活在理想的世界中,有时,即使对模式进行“小”更改也会对应用程序逻辑产生重大的连锁反应。

考虑具有SSN列(和哑主键)的Customer表的情况,以及还包含SSN列(由Customer数据中的业务逻辑填充,但不存在FK)的Claim表的情况。该设计有缺陷,但是已经使用了几年,并且已经在该架构上构建了三个不同的应用程序。显然,删除Claim.SSN并建立真实的PK-FK关系将是理想的,但也将是一个重大的改革。另一方面,在Customer.SSN上设置UNIQUE约束,并在Claim.SSN上添加FK,可以提供参照完整性,而对应用程序的影响很小或没有影响。

不要误会我的意思,我全都是为了规范,但有时实用主义胜过理想主义。如果创可贴可以帮助平庸的设计,那么就可以避免手术。


18

死灵法师。
我假设有人登陆时,他需要一个外键才能在包含非唯一键的表中进行列化。

问题是,如果遇到该问题,数据库架构将被非规范化。

例如,您将一个房间保留在一个表中,其中包含一个房间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)中。
但是要禁止无效值,您需要设置一个外键,否则,数据垃圾是早日产生的结果,而不是晚于结果。

现在,在这种情况下(不重写整个应用程序),您可以执行的操作是插入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

总是迟到聚会...但是感谢这个真实的建议-我确实有这样的建议-辅助表中的数据已版本化(除了键之外还具有日期范围),我只想链接最新版本从我的主要餐桌上…
伊恩

1
真实的建议!我可以想象很多遗留应用程序的场景,其中由于某种原因或其他原因,“最佳实践”是不可能的,并且检查约束可以很好地工作。
ryanwc


2

如果表是一对多关系,则主键始终必须是唯一的,外键则需要允许非唯一值。如果表是通过一对一关系而不是一对多关系连接的,则将外键用作主键是完全可以的。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.