Klucz obcy do klucza innego niż podstawowy

136

Mam tabelę, która przechowuje dane, a jeden z tych wierszy musi istnieć w innej tabeli. Dlatego chcę, aby klucz obcy zachował integralność referencyjną.

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

Jednak, jak widać, tabela, do której mam klucz obcy, nie jest kolumną PK. Czy istnieje sposób na utworzenie tego klucza obcego, czy może lepszy sposób na zachowanie tej referencyjnej integralności?

Craig
źródło
Nie ma to większego sensu. Dlaczego nie odnieść się do table1.ID?
zerkms
jest ostateczne, że jeśli twój AnothidID nie jest kluczem podstawowym, powinien to być ForeignKey, więc będąc kluczem obcym, twoja tabela2 powinna wskazywać na tę samą tabelę (możliwa tabela3)
Roger Barreto

Odpowiedzi:

182

Jeśli naprawdę chcesz utworzyć klucz obcy dla klucza innego niż podstawowy, MUSI to być kolumna z unikalnym ograniczeniem.

Z Books Online :

Ograniczenie FOREIGN KEY nie musi być połączone tylko z ograniczeniem PRIMARY KEY w innej tabeli; można go również zdefiniować jako odniesienie do kolumn ograniczenia UNIQUE w innej tabeli.

Więc w twoim przypadku, jeśli stworzysz AnotherIDunikalny, będzie to dozwolone. Jeśli nie możesz zastosować unikalnego ograniczenia, nie masz szczęścia, ale to naprawdę ma sens, jeśli się nad tym zastanowić.

Chociaż, jak już wspomniano, jeśli masz doskonale dobry klucz podstawowy jako klucz kandydujący, dlaczego nie użyć go?

Ian Preston
źródło
1
W odniesieniu do twojego ostatniego pytania ... Mam sytuację, w której chciałbym, aby złożone klucze kandydujące były kluczem podstawowym tylko dlatego, że ma większe znaczenie semantyczne i najlepiej opisuje mój model. Ja również chciałbym mieć odniesienie do klucza obcego, nowo utworzonego klucza zastępczego ze względu na wydajność (jak wspomniano powyżej). Czy ktoś przewiduje jakieś problemy z taką konfiguracją?
Daniel Macias
Proszę pana, czy może pan powiedzieć, jaka jest logika stojąca za tym, że klucz obcy zawsze odwołuje się do atrybutu z unikalnym ograniczeniem?
Shivangi Gupta
Jak to zrobić w asp net MVC 5
irfandar
Czy w innej tabeli można zadeklarować zwykłą liczbę całkowitą klucza innego niż podstawowy? Jak ten. czy to możliwe? 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 Pracownik (EmpID) , CONSTRAINT FK_Project2 FOREIGN KEY (StEng) REFERENCES Employee (EmpID),)
Nabid
19

Jak zauważyli inni, idealnie byłoby, gdyby klucz obcy został utworzony jako odniesienie do klucza podstawowego (zwykle kolumny TOŻSAMOŚĆ). Jednak nie żyjemy w idealnym świecie i czasami nawet „mała” zmiana w schemacie może mieć znaczący wpływ na logikę aplikacji.

Rozważmy przypadek tabeli Customer z kolumną SSN (i głupim kluczem podstawowym) oraz tabeli Claim, która zawiera również kolumnę SSN (wypełniona logiką biznesową z danych klienta, ale nie istnieje FK). Projekt jest wadliwy, ale był używany przez kilka lat, a na schemacie zbudowano trzy różne aplikacje. Powinno być oczywiste, że wyrzucenie Claim.SSN i ​​nawiązanie prawdziwej relacji PK-FK byłoby idealne, ale byłoby też znaczącym remontem. Z drugiej strony nałożenie UNIQUE ograniczenia na Customer.SSN i ​​dodanie FK na Claim.SSN może zapewnić integralność referencyjną, z niewielkim lub żadnym wpływem na aplikacje.

Nie zrozum mnie źle, jestem za normalizacją, ale czasami pragmatyzm wygrywa z idealizmem. Jeśli przeciętnemu projektowi można pomóc za pomocą opaski, można uniknąć operacji.

EJSawyer
źródło
18

Nekromancja.
Zakładam, że kiedy ktoś tu ląduje, potrzebuje klucza obcego do kolumny w tabeli zawierającej klucze nieunikalne.

Problem polega na tym, że jeśli masz ten problem, schemat bazy danych jest zdenormalizowany.

Na przykład przechowujesz pokoje w tabeli z kluczem podstawowym identyfikatora pokoju, polem DateFrom i DateTo oraz innym uid, tutaj RM_ApertureID, aby śledzić ten sam pokój, i polem do usuwania nietrwałego, takiego jak RM_Status, gdzie 99 oznacza „usunięte”, a <> 99 oznacza „aktywne”.

Więc kiedy tworzysz pierwsze pomieszczenie, wstaw RM_UID i RM_ApertureID jako te same wartości co RM_UID. Następnie, gdy zamkniesz pokój na określoną datę i ustawisz go ponownie z nowym zakresem dat, RM_UID to newid (), a RM_ApertureID z poprzedniego wpisu stanie się nowym RM_ApertureID.

Więc jeśli tak jest, RM_ApertureID jest polem nieunikalnym, więc nie możesz ustawić klucza obcego w innej tabeli.

I nie ma możliwości ustawienia klucza obcego na nieunikalną kolumnę / indeks, np. W T_ZO_REM_AP_Raum_Reinigung (gdzie RM_UID to faktycznie RM_ApertureID).
Ale aby zabronić nieprawidłowych wartości, musisz ustawić klucz obcy, w przeciwnym razie raczej prędzej niż później wynikiem będzie śmieci danych ...

Teraz, co możesz zrobić w tym przypadku (oprócz przepisania całej aplikacji), to wstawienie ograniczenia CHECK z funkcją skalarną sprawdzającą obecność klucza:

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
Stefan Steiger
źródło
Zawsze spóźniam się na imprezę ... Ale dzięki za tę radę ze świata rzeczywistego - mam dokładnie to - dane w tabeli pomocniczej są wersjonowane (oprócz klucza ma zakres dat) i chcę połączyć tylko najnowszą wersję z mojego głównego stołu ...
Ian
1
Niezła rada dotycząca świata rzeczywistego! Mogę sobie wyobrazić wiele scenariuszy ze starszymi aplikacjami, w których „najlepsza praktyka” nie jest możliwa z tego czy innego powodu, a ograniczenie sprawdzające działałoby dobrze.
ryanwc
2

Klucze podstawowe zawsze muszą być unikalne, klucze obce muszą zezwalać na wartości nieunikalne, jeśli tabela jest relacją jeden do wielu. Użycie klucza obcego jako klucza podstawowego jest w porządku, jeśli tabela jest połączona relacją jeden do jednego, a nie relacją jeden do wielu.

Ograniczenie FOREIGN KEY nie musi być połączone tylko z ograniczeniem PRIMARY KEY w innej tabeli; można go również zdefiniować jako odniesienie do kolumn ograniczenia UNIQUE w innej tabeli.

Anzeem SN
źródło