非主键的外键
我有一个保存数据的表,其中一个行需要存在另一个表中。 所以,我想要一个外键保持参照完整性。
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。 有没有办法创build这个外键,或者更好的方法来维护这个参照完整性?
如果你真的想创build一个非主键的外键,它必须是一个具有唯一约束的列。
来自联机丛书 :
FOREIGN KEY约束不必仅链接到另一个表中的PRIMARY KEY约束; 它也可以被定义为引用另一个表中的UNIQUE约束的列。
所以在你的情况下,如果你使AnotherID
独一无二,它将被允许。 如果你不能申请一个独特的约束,那么你的运气不好,但是如果你仔细想一想,这确实是有道理的。
虽然,如前所述,如果你有一个非常好的主键作为候选键,为什么不使用它呢?
正如其他人指出的,理想情况下,外键将被创build为对主键(通常是一个IDENTITY列)的引用。 然而,我们并不是生活在一个理想的世界中,有时候甚至是一个“小”的模式变化都会对应用逻辑产生重大的影响。
考虑具有SSN列(和哑主键)的Customer表以及还包含SSN列(由来自Customer数据的业务逻辑填充,但不存在FK)的Claim表。 这个devise是有缺陷的,但已经使用了好几年,在这个模式上已经build立了三个不同的应用程序。 很明显,把Claim.SSN和PK-FK联系起来是理想的,但也是一个重大的改变。 另一方面,在Customer.SSN上添加一个UNIQUE约束,并在Claim.SSN上添加一个FK,可以提供参照完整性,而对应用程序几乎没有影响。
不要误解我的意思,我只是为了正常化,但有时候实用主义胜过理想主义。 如果一个平庸的devise可以帮助一个创可贴,可以避免手术。
Necromancing。
我假设有人在这里登陆时,他需要一个外键在包含非唯一键的表中列出。
问题是,如果你有这个问题,数据库模式是非规范化的。
例如,您可以将房间保存在一个表格中,其中包含room-uid主键,DateFrom和DateTo字段以及另一个uid,这里是RM_ApertureID以跟踪同一个房间,以及软删除字段(如RM_Status)其中99表示“删除”,99表示“有效”。
因此,在创build第一个房间时,将RM_UID和RM_ApertureID作为与RM_UID相同的值插入。 然后,当您将房间终止到一个date,并重新build立一个新的date范围时,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