非主キーへの外部キー


136

データを保持するテーブルがあり、それらの行の1つが別のテーブルに存在する必要があります。したがって、参照整合性を維持するために外部キーが必要です。

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が主キーでない場合は、ForeignKeyでなければならないので、foreignKeyであるため、table2は同じテーブル(可能なtable3)を指す必要があります
Roger Barreto

回答:


182

非主キーへの外部キーを作成したい場合は、一意の制約を持つ列である必要があります。

Books Onlineから:

FOREIGN KEY制約は、別のテーブルのPRIMARY KEY制約にのみリンクする必要はありません。別のテーブルのUNIQUE制約の列を参照するように定義することもできます。

だからあなたの場合あなたが作るなら AnotherIDユニークにする、それは許されます。一意の制約を適用できない場合、あなたは運が悪いですが、それを考えれば、これは本当に意味があります。

すでに述べたように、候補キーとして完全に優れた主キーがある場合は、それを使用してみませんか?


1
あなたの最後の質問に関連して... 意味的に重要性が高く、モデルを最もよく説明しているという理由だけで、複合候補キーを主キーにしたい状況があります。(上記のように)パフォーマンスのために、新しく作成したサロゲートキーを外部キーで参照したいのですが。誰かがそのようなセットアップで何か問題を予測していますか?
Daniel Macias

その外部キーの背後にあるロジックは常に一意の制約を持つ属性を参照していますか教えていただけますか
Shivangi Gupta 2017

ネットMVC 5 ASPでこれを行う方法
irfandar

通常の非主キー整数を他のテーブルで外部キーとして宣言できますか?このように。これは可能ですか?CREATE TABLE Project(PSLNO Numeric(8,0)Not Null、PrMan Numeric(8,0)、StEng Numeric(8,0)、CONSTRAINT PK_Project PRIMARY KEY(PSLNO)、CONSTRAINT FK_Project1 FOREIGN KEY(PrMan)REFERENCES Employee(EmpID) 、CONSTRAINT FK_Project2 FOREIGN KEY(StEng)REFERENCES Employee(EmpID)、)
Nabid

19

他の人が指摘したように、理想的には、外部キーは主キー(通常はIDENTITY列)への参照として作成されます。ただし、私たちは理想的な世界に住んでいるわけではなく、スキーマへの「小さな」変更でさえ、アプリケーションロジックに大きな波及効果をもたらすことがあります。

SSN列(およびダムの主キー)を持つCustomerテーブルと、SSN列(Customerデータからビジネスロジックによって入力されたがFKが存在しない)も含むClaimテーブルの場合を考えてみます。この設計には欠陥がありますが、数年間使用されており、3つの異なるアプリケーションがスキーマに基づいて構築されています。Claim.SSNを取り除き、実際のP​​K-FK関係を配置することが理想的であることは明らかですが、オーバーホールにです。一方、Customer.SSNにUNIQUE制約を設定し、Claim.SSNにFKを追加すると、アプリケーションにほとんどまたはまったく影響を与えずに、参照整合性を提供できます。

私を誤解しないでください、私はすべて正規化のためですが、時には実用主義が理想主義に勝ちます。平凡なデザインがバンドエイドで助けられるなら、手術は避けられるかもしれません。


18

ネクロマンシング。
誰かがここに着くと、一意でないキーを含むテーブルの列に外部キーが必要になると思います。

問題は、その問題がある場合、データベーススキーマが非正規化されることです。

たとえば、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(例: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

常にパーティーに遅れます...しかし、実際のアドバイスに感謝します-私はまさにそれを持っています-セカンダリテーブルのデータはバージョン管理されており(キーに加えて日付範囲があります)、最新バージョンのみをリンクしたい私の主なテーブルから...
Ian

1
素晴らしい現実のアドバイス!何らかの理由で「ベストプラクティス」を実行できず、チェック制約が適切に機能するレガシーアプリケーションのシナリオはたくさんあります。
ryanwc 2018年

このソリューションは信頼できません。参照:dba.stackexchange.com/.../how-are-my-sql-server-constraints-being-bypassed
stomy

2

テーブルが1対多の関係である場合、主キーは常に一意である必要があり、外部キーは一意でない値を許可する必要があります。テーブルが1対多の関係ではなく、1対1の関係で接続されている場合は、主キーとして外部キーを使用しても問題ありません。

FOREIGN KEY制約は、別のテーブルのPRIMARY KEY制約にのみリンクする必要はありません。別のテーブルのUNIQUE制約の列を参照するように定義することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.