非主键的外键

Cra*_*aig 120 sql sql-server

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

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)
)
Run Code Online (Sandbox Code Playgroud)

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

Ian*_*ton 167

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

来自联机丛书:

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

所以在你的情况下,如果你做出AnotherID独特的,它将被允许.如果你不能应用一个独特的约束你运气不好,但如果你考虑它,这确实有意义.

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

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

Ste*_*ger 16

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).
但是为了禁止无效值,你需要设置一个外键,否则,数据垃圾是结果而不是以后...

现在你在这种情况下可以做的事情(没有重写整个应用程序)是插入一个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
Run Code Online (Sandbox Code Playgroud)


小智 15

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

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

不要误解我,我都是为了正常化,但有时候实用主义胜过理想主义.如果可以通过创可贴帮助平庸的设计,可以避免手术.


Anz*_*S N 7

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

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