[sql] 기본 키가 아닌 외래 키

데이터를 보유하는 테이블이 있으며 해당 행 중 하나가 다른 테이블에 있어야합니다. 따라서 참조 무결성을 유지하기 위해 외래 키를 원합니다.

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

그러나 보시다시피, I 외래 키 테이블은 열이 PK가 아닙니다. 이 외래 키를 만드는 방법이 있습니까, 아니면이 참조 무결성을 유지하는 더 좋은 방법이 있습니까?



답변

기본이 아닌 키에 외래 키를 만들려면 고유 한 제약 조건이있는 열이어야합니다.

에서 온라인 :

FOREIGN KEY 제약 조건은 다른 테이블의 PRIMARY KEY 제약 조건에만 연결될 필요는 없습니다. 다른 테이블에서 UNIQUE 제약 조건의 열을 참조하도록 정의 할 수도 있습니다.

따라서 귀하의 경우에 AnotherID독특하게 만들면 허용됩니다. 고유 한 제약 조건을 적용 할 수 없으면 운이 좋지 않지만 생각하면 말이됩니다.

언급했듯이, 후보 키로 완벽하게 좋은 기본 키가 있다면 그것을 사용하지 않는 이유는 무엇입니까?


답변

다른 사람들이 지적했듯이 외래 키는 기본 키 (일반적으로 IDENTITY 열)에 대한 참조로 생성되는 것이 이상적입니다. 그러나 우리는 이상적인 세상에 살고 있지 않으며 때로는 스키마에 대한 “작은”변경조차도 응용 프로그램 논리에 큰 파급 효과를 줄 수 있습니다.

SSN 열 (및 벙어리 기본 키)이있는 Customer 테이블과 SSN 열도 포함하는 Claim 테이블 (고객 데이터의 비즈니스 논리에 의해 채워지지만 FK는없는 경우)을 고려하십시오. 디자인에는 결함이 있지만 수년 동안 사용되어 왔으며 3 개의 다른 응용 프로그램이 스키마에 구축되었습니다. Claim.SSN을 제거하고 실제 PK-FK 관계를 맺는 것이 이상적 일 것임은 분명 하지만, 또한 대대적 인 점검이 될 것 입니다. 반면에 Customer.SSN에 UNIQUE 제약 조건을 적용하고 Claim.SSN에 FK를 추가하면 응용 프로그램에 거의 영향을 미치지 않으면 서 참조 무결성을 제공 할 수 있습니다.

나를 잘못 이해하지 마라, 나는 정상화에 대한 모든 것이지만 때로는 실용주의가 이상주의보다 승리한다. 반창고로 평범한 디자인을 도울 수 있다면 수술을 피할 수 있습니다.


답변

괴롭힘.
누군가가 여기에 도착하면 고유하지 않은 키가 포함 된 테이블의 열에 외래 키가 필요하다고 가정합니다.

문제는 해당 문제가있는 경우 데이터베이스 스키마가 비정규 화 된 것입니다.

예를 들어 room-uid 기본 키, DateFrom 및 DateTo 필드 및 다른 uid (여기서 동일한 룸을 추적하기위한 RM_ApertureID) 및 RM_Status와 같은 소프트 삭제 필드를 사용하여 테이블에 룸을 유지하고 있습니다. 여기서 99는 ‘삭제됨’을 의미하고 <> 99는 ‘활성’을 의미합니다.

따라서 첫 번째 방을 만들 때 RM_UID와 동일한 값으로 RM_UID 및 RM_ApertureID를 삽입합니다. 그런 다음 회의실을 날짜로 종료하고 새 날짜 범위로 다시 설정하면 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


답변

기본 키는 항상 고유해야하며 외래 키는 테이블이 일대 다 관계인 경우 고유하지 않은 값을 허용해야합니다. 테이블이 일대 다 관계가 아닌 일대일 관계로 연결된 경우 외래 키를 기본 키로 사용하는 것이 좋습니다.

FOREIGN KEY 제약 조건은 다른 테이블의 PRIMARY KEY 제약 조건에만 연결될 필요는 없습니다. 다른 테이블에서 UNIQUE 제약 조건의 열을 참조하도록 정의 할 수도 있습니다.


답변