169

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

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。有没有办法创建这个外键,或者有更好的方法来维护这个引用完整性?

4

5 回答 5

231

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

来自在线书籍

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

因此,在您的情况下,如果您制作AnotherID独特,它将被允许。如果你不能应用一个独特的约束,你就不走运了,但如果你仔细想想,这确实是有道理的。

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

于 2013-08-26T00:36:24.843 回答
25

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

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

不要误会我的意思,我完全赞成正常化,但有时实用主义会战胜理想主义。如果可以用创可贴帮助平庸的设计,则可以避免手术。

于 2014-05-15T20:47:54.083 回答
20

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

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

例如,您将房间保存在一个表中,带有一个房间 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 中(其中 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
于 2014-10-17T15:03:52.570 回答
5

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

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

于 2019-04-11T06:09:59.130 回答
-1

是的,您通常至少会将其编入索引。

create table student(
    id int,
    name varchar(30),
    index inName(id)
);

CREATE TABLE grade(
    id int,
    subject varchar(30),
    mark double,
    foreign key(id) references student(id)
);
于 2021-08-30T18:44:34.033 回答