Chiave esterna per chiave non primaria


136

Ho una tabella che contiene i dati e una di quelle righe deve esistere in un'altra tabella. Quindi, voglio una chiave esterna per mantenere l'integrità referenziale.

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

Tuttavia, come puoi vedere, la chiave esterna della tabella in cui mi trovo, la colonna non è la PK. C'è un modo per creare questa chiave esterna o forse un modo migliore per mantenere questa integrità referenziale?


Non ha molto senso farlo. Perché non fare riferimento a table1.ID?
zerkms,

è definitivo che se il tuo AnothidID non è una chiave primaria dovrebbe essere un ForeignKey, quindi essendo un ForeignKey, il tuo table2 dovrebbe puntare alla stessa tabella (possibile table3)
Roger Barreto

Risposte:


182

Se vuoi davvero creare una chiave esterna per una chiave non primaria, DEVE essere una colonna con un vincolo univoco.

Dai libri online :

Un vincolo FOREIGN KEY non deve essere collegato solo a un vincolo PRIMARY KEY in un'altra tabella; può anche essere definito per fare riferimento alle colonne di un vincolo UNIQUE in un'altra tabella.

Quindi, nel tuo caso, se rendi AnotherIDunico, sarà permesso. Se non riesci ad applicare un vincolo unico, sei sfortunato, ma questo ha davvero senso se ci pensi.

Anche se, come è stato detto, se hai una chiave primaria perfettamente valida come chiave candidata, perché non usarla?


1
In relazione alla tua ultima domanda ... Ho una situazione in cui vorrei che le chiavi composte candidate fossero la chiave primaria solo perché semanticamente ha più importanza e descrive meglio il mio modello. Anch'io vorrei avere una chiave esterna come riferimento a una chiave surrogata appena creata per motivi di prestazioni (come notato sopra). Qualcuno prevede problemi con una tale configurazione?
Daniel Macias,

Signore, per favore, puoi dire qual è la logica alla base di quella chiave esterna che fa sempre riferimento all'attributo con vincolo univoco?
Shivangi Gupta,

Come fare questo in asp net MVC 5
irfandar

Il normale numero intero di chiave non primaria può essere dichiarato chiave esterna in un'altra tabella? Come questo. È possibile? Progetto CREATE TABLE (PSLNO Numereric (8,0) Not Null, PrMan Numereric (8,0), StEng Numeric (8,0), CONSTRAINT PK_Project PRIMARY KEY (PSLNO), CONSTRAINT FK_Project1 FOREIGN KEY (PrMan) REFERENCES Dipendente (EmpID) , VINCOLO FK_Project2 FOREIGN KEY (STENG) RIFERIMENTI dipendenti (EmpID))
Nabid

19

Come altri hanno sottolineato, idealmente, la chiave esterna dovrebbe essere creata come riferimento a una chiave primaria (di solito una colonna IDENTITY). Tuttavia, non viviamo in un mondo ideale e talvolta anche una "piccola" modifica a uno schema può avere significativi effetti a catena sulla logica dell'applicazione.

Si consideri il caso di una tabella Customer con una colonna SSN (e una chiave primaria stupida) e una tabella Claim che contenga anche una colonna SSN (popolata dalla logica aziendale dai dati del Cliente, ma non esiste alcun FK). Il design è imperfetto, ma è in uso da diversi anni e sullo schema sono state create tre diverse applicazioni. Dovrebbe essere ovvio che strappare Claim.SSN e instaurare una vera relazione PK-FK sarebbe l'ideale, ma sarebbe anche una revisione significativa . D'altra parte, mettere un vincolo UNICO su Customer.SSN e aggiungere un FK su Claim.SSN, potrebbe fornire integrità referenziale, con un impatto minimo o nullo sulle applicazioni.

Non fraintendetemi, sono tutto per la normalizzazione, ma a volte il pragmatismo vince sull'idealismo. Se un design mediocre può essere aiutato con un cerotto, la chirurgia potrebbe essere evitata.


18

Necromancing.
Presumo che quando qualcuno atterra qui, abbia bisogno di una chiave esterna da colonna in una tabella che contenga chiavi non univoche.

Il problema è che se si ha quel problema, lo schema del database viene denormalizzato.

Ad esempio, tieni le stanze in una tabella, con una chiave primaria room-uid, un campo DateFrom e DateTo e un altro uid, qui RM_ApertureID per tenere traccia della stessa room e un campo soft-delete, come RM_Status, dove 99 significa "cancellato" e <> 99 significa "attivo".

Quindi quando crei la prima stanza, inserisci RM_UID e RM_ApertureID come lo stesso valore di RM_UID. Quindi, quando si termina la room a una data e si ristabilisce con un nuovo intervallo di date, RM_UID è newid () e RM_ApertureID dalla voce precedente diventa il nuovo RM_ApertureID.

Quindi, in tal caso, RM_ApertureID è un campo non univoco, quindi non è possibile impostare una chiave esterna in un'altra tabella.

E non c'è modo di impostare una chiave esterna su una colonna / indice non univoco, ad esempio in T_ZO_REM_AP_Raum_Reinigung (DOVE RM_UID è in realtà RM_ApertureID).
Ma per proibire i valori non validi, è necessario impostare una chiave esterna, altrimenti, data-garbage è il risultato prima piuttosto che dopo ...

Ora, ciò che puoi fare in questo caso (a meno di riscrivere l'intera applicazione) è inserire un vincolo CHECK, con una funzione scalare che controlla la presenza della chiave:

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

Sempre in ritardo alla festa ... Ma grazie per questo consiglio del mondo reale - ho esattamente questo - i dati nella tabella secondaria sono sottoposti a versione (ha un intervallo di date oltre a una chiave) e voglio solo collegare l'ultima versione dalla mia tabella principale ...
Ian,

1
Bel consiglio nel mondo reale! Posso immaginare molti scenari con applicazioni legacy in cui la "best practice" non è possibile per un motivo o per l'altro e il vincolo di controllo funzionerebbe bene.
ryanwc,


2

Le chiavi primarie devono sempre essere univoche, le chiavi esterne devono consentire valori non univoci se la tabella è una relazione uno-a-molti. È perfettamente corretto utilizzare una chiave esterna come chiave primaria se la tabella è connessa da una relazione uno a uno, non da una a una.

Un vincolo FOREIGN KEY non deve essere collegato solo a un vincolo PRIMARY KEY in un'altra tabella; può anche essere definito per fare riferimento alle colonne di un vincolo UNIQUE in un'altra tabella.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.