Fremdschlüssel zum Nicht-Primärschlüssel

136

Ich habe eine Tabelle, die Daten enthält, und eine dieser Zeilen muss in einer anderen Tabelle vorhanden sein. Ich möchte also, dass ein Fremdschlüssel die referenzielle Integrität beibehält.

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

Wie Sie jedoch sehen können, ist die Spalte in der Tabelle, für die ich einen Fremdschlüssel habe, nicht die PK. Gibt es eine Möglichkeit, diesen Fremdschlüssel zu erstellen, oder eine bessere Möglichkeit, diese referenzielle Integrität aufrechtzuerhalten?

Craig
quelle
Das macht nicht viel Sinn. Warum nicht beziehen table1.ID?
Zerkms
Es ist definitiv, dass wenn Ihre AnothidID kein Primärschlüssel ist, es ein ForeignKey sein sollte. Als ForeignKey sollte Ihre Tabelle2 also auf dieselbe Tabelle zeigen (mögliche Tabelle3)
Roger Barreto

Antworten:

182

Wenn Sie wirklich einen Fremdschlüssel für einen Nicht-Primärschlüssel erstellen möchten, MUSS es sich um eine Spalte handeln, die eine eindeutige Einschränkung aufweist.

Aus Online-Büchern :

Eine FOREIGN KEY-Einschränkung muss nicht nur mit einer PRIMARY KEY-Einschränkung in einer anderen Tabelle verknüpft sein. Es kann auch definiert werden, um auf die Spalten einer UNIQUE-Einschränkung in einer anderen Tabelle zu verweisen.

Wenn Sie also in Ihrem Fall AnotherIDUnikate erstellen, ist dies zulässig. Wenn Sie keine eindeutige Einschränkung anwenden können, haben Sie kein Glück, aber dies ist wirklich sinnvoll, wenn Sie darüber nachdenken.

Obwohl, wie bereits erwähnt, wenn Sie einen vollkommen guten Primärschlüssel als Kandidatenschlüssel haben, warum nicht diesen verwenden?

Ian Preston
quelle
1
Bezogen auf Ihre letzte Frage ... Ich habe eine Situation, in der ich möchte, dass zusammengesetzte Kandidatenschlüssel der Primärschlüssel sind, nur weil er semantisch wichtiger ist und mein Modell am besten beschreibt. Ich möchte auch, dass ein Fremdschlüssel aus Gründen der Leistung (wie oben erwähnt) auf einen neu erstellten Ersatzschlüssel verweist. Hat jemand Probleme mit einem solchen Setup?
Daniel Macias
Sir, können Sie bitte sagen, welche Logik hinter diesem Fremdschlüssel immer auf Attribute mit eindeutigen Einschränkungen verweist?
Shivangi Gupta
Wie das geht in asp net MVC 5
irfandar
Kann eine normale Ganzzahl ohne Primärschlüssel in einer anderen Tabelle als Fremdschlüssel deklariert werden? Wie dieser. Ist das möglich? CREATE TABLE-Projekt (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 , CONSTRAINT FK_Project2 AUSLÄNDISCHER SCHLÜSSEL (STEng) REFERENZEN Mitarbeiter (EmpID),)
Nabid
19

Wie andere bereits betont haben, wird der Fremdschlüssel im Idealfall als Referenz auf einen Primärschlüssel (normalerweise eine IDENTITY-Spalte) erstellt. Wir leben jedoch nicht in einer idealen Welt, und manchmal kann sogar eine "kleine" Änderung eines Schemas erhebliche Auswirkungen auf die Anwendungslogik haben.

Betrachten Sie den Fall einer Kundentabelle mit einer SSN-Spalte (und einem dummen Primärschlüssel) und einer Anspruchstabelle, die auch eine SSN-Spalte enthält (gefüllt mit Geschäftslogik aus den Kundendaten, aber keine FK vorhanden). Das Design ist fehlerhaft, wird jedoch seit mehreren Jahren verwendet, und drei verschiedene Anwendungen wurden auf dem Schema erstellt. Es sollte offensichtlich sein, dass es ideal wäre, Claim.SSN herauszureißen und eine echte PK-FK-Beziehung aufzubauen, aber auch eine bedeutende Überarbeitung wäre. Auf der anderen Seite kann das Festlegen einer EINZIGARTIGEN Einschränkung für Customer.SSN und das Hinzufügen eines FK für Claim.SSN eine referenzielle Integrität mit geringen oder keinen Auswirkungen auf die Anwendungen gewährleisten.

Versteh mich nicht falsch, ich bin alles für Normalisierung, aber manchmal gewinnt Pragmatismus über Idealismus. Wenn einem mittelmäßigen Design mit einem Pflaster geholfen werden kann, kann eine Operation vermieden werden.

EJSawyer
quelle
18

Nekromantie.
Ich gehe davon aus, dass jemand, der hier landet, einen Fremdschlüssel benötigt, um in einer Tabelle zu spalten, die nicht eindeutige Schlüssel enthält.

Das Problem ist, dass das Datenbankschema denormalisiert wird, wenn Sie dieses Problem haben.

Sie behalten beispielsweise Räume in einer Tabelle mit einem Primärschlüssel für die Raum-UID, einem DateFrom- und einem DateTo-Feld sowie einer anderen UID, hier RM_ApertureID, um denselben Raum zu verfolgen, und einem Soft-Delete-Feld wie RM_Status. Dabei bedeutet 99 "gelöscht" und <> 99 "aktiv".

Wenn Sie also den ersten Raum erstellen, fügen Sie RM_UID und RM_ApertureID als denselben Wert wie RM_UID ein. Wenn Sie dann den Raum auf ein Datum beenden und ihn mit einem neuen Datumsbereich wiederherstellen, ist RM_UID newid (), und die RM_ApertureID aus dem vorherigen Eintrag wird zur neuen RM_ApertureID.

In diesem Fall ist RM_ApertureID ein nicht eindeutiges Feld, sodass Sie keinen Fremdschlüssel in einer anderen Tabelle festlegen können.

Und es gibt keine Möglichkeit, einen Fremdschlüssel auf eine nicht eindeutige Spalte / einen nicht eindeutigen Index zu setzen, z. B. in T_ZO_REM_AP_Raum_Reinigung (WHERE RM_UID ist tatsächlich RM_ApertureID).
Um ungültige Werte zu verhindern, müssen Sie einen Fremdschlüssel festlegen. Andernfalls ist Datenmüll eher früher als später die Folge ...

In diesem Fall können Sie nun (ohne die gesamte Anwendung neu zu schreiben) eine CHECK-Einschränkung einfügen, wobei eine Skalarfunktion das Vorhandensein des Schlüssels überprüft:

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
quelle
Immer zu spät zur Party ... Aber danke für diesen Ratschlag aus der Praxis - genau das habe ich - die Daten in der sekundären Tabelle sind versioniert (haben zusätzlich zu einem Schlüssel einen Datumsbereich) und ich möchte nur die neueste Version verknüpfen von meinem Haupttisch ...
Ian
1
Netter Rat in der Praxis! Ich kann mir viele Szenarien mit Legacy-Anwendungen vorstellen, in denen die "Best Practice" aus dem einen oder anderen Grund nicht möglich ist und die Prüfbeschränkung gut funktionieren würde.
Ryanwc
2

Primärschlüssel müssen immer eindeutig sein, Fremdschlüssel müssen nicht eindeutige Werte zulassen, wenn die Tabelle eine Eins-zu-Viele-Beziehung ist. Es ist vollkommen in Ordnung, einen Fremdschlüssel als Primärschlüssel zu verwenden, wenn die Tabelle durch eine Eins-zu-Eins-Beziehung verbunden ist, nicht durch eine Eins-zu-Viele-Beziehung.

Eine FOREIGN KEY-Einschränkung muss nicht nur mit einer PRIMARY KEY-Einschränkung in einer anderen Tabelle verknüpft sein. Es kann auch definiert werden, um auf die Spalten einer UNIQUE-Einschränkung in einer anderen Tabelle zu verweisen.

Anzeem SN
quelle