Clé étrangère vers une clé non primaire

136

J'ai une table qui contient des données, et l'une de ces lignes doit exister dans une autre table. Donc, je veux une clé étrangère pour maintenir l'intégrité référentielle.

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

Cependant, comme vous pouvez le voir, la table dans laquelle je clé étrangère, la colonne n'est pas le PK. Existe-t-il un moyen de créer cette clé étrangère, ou peut-être un meilleur moyen de maintenir cette intégrité référentielle?

Craig
la source
Cela n'a pas beaucoup de sens de faire cela. Pourquoi ne pas y faire référence table1.ID?
zerkms
il est définitif que si votre AnothidID n'est pas une clé primaire, il devrait être une ForeignKey, donc étant une ForeignKey, votre table2 devrait pointer vers la même table (table3 possible)
Roger Barreto

Réponses:

182

Si vous voulez vraiment créer une clé étrangère vers une clé non primaire, il DOIT être une colonne qui a une contrainte unique dessus.

À partir de Books Online :

Une contrainte FOREIGN KEY ne doit pas être liée uniquement à une contrainte PRIMARY KEY dans une autre table; il peut également être défini pour référencer les colonnes d'une contrainte UNIQUE dans une autre table.

Donc dans votre cas si vous faites AnotherIDunique, cela sera autorisé. Si vous ne pouvez pas appliquer une contrainte unique, vous n'avez pas de chance, mais cela a vraiment du sens si vous y réfléchissez.

Cependant, comme cela a été mentionné, si vous avez une clé primaire parfaitement bonne comme clé candidate, pourquoi ne pas l'utiliser?

Ian Preston
la source
1
En lien avec votre dernière question ... J'ai une situation où j'aimerais que les clés candidates composites soient la clé primaire simplement parce qu'elles ont plus d'importance sémantiquement et décrivent le mieux mon modèle. Je voudrais aussi avoir une référence de clé étrangère une clé de substitution nouvellement créée pour des raisons de performance (comme indiqué ci-dessus). Quelqu'un prévoit-il des problèmes avec une telle configuration?
Daniel Macias
Monsieur, pouvez-vous s'il vous plaît dire quelle est la logique derrière cette clé étrangère fait toujours référence à l'attribut avec une contrainte unique?
Shivangi Gupta
Comment faire cela dans asp net MVC 5
irfandar
Un entier de clé non primaire normal peut-il être déclaré comme clé étrangère dans une autre table? Comme celui-ci. Est-ce possible? Projet CREATE TABLE (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

Comme d'autres l'ont souligné, idéalement, la clé étrangère serait créée en tant que référence à une clé primaire (généralement une colonne IDENTITY). Cependant, nous ne vivons pas dans un monde idéal, et parfois même une «petite» modification d'un schéma peut avoir des effets d'entraînement significatifs sur la logique de l'application.

Prenons le cas d'une table Customer avec une colonne SSN (et une clé primaire stupide) et une table Claim qui contient également une colonne SSN (remplie par la logique métier à partir des données Customer, mais aucun FK n'existe). La conception est imparfaite, mais elle est utilisée depuis plusieurs années et trois applications différentes ont été construites sur le schéma. Il devrait être évident que déchirer Claim.SSN et mettre en place une vraie relation PK-FK serait idéal, mais serait également une refonte importante . D'autre part, mettre une contrainte UNIQUE sur Customer.SSN, et ajouter un FK sur Claim.SSN, pourrait fournir une intégrité référentielle, avec peu ou pas d'impact sur les applications.

Ne vous méprenez pas, je suis tout à fait pour la normalisation, mais parfois le pragmatisme l'emporte sur l'idéalisme. Si une conception médiocre peut être aidée avec un pansement, la chirurgie peut être évitée.

EJSawyer
la source
18

Nécromancie.
Je suppose que lorsque quelqu'un atterrit ici, il a besoin d'une clé étrangère pour colonne dans une table qui contient des clés non uniques.

Le problème est que si vous rencontrez ce problème, le schéma de base de données est dénormalisé.

Vous conservez par exemple des salles dans une table, avec une clé primaire room-uid, un champ DateFrom et DateTo, et un autre uid, ici RM_ApertureID pour garder une trace de la même room, et un champ soft-delete, comme RM_Status, où 99 signifie «supprimé» et <> 99 signifie «actif».

Ainsi, lorsque vous créez la première salle, vous insérez RM_UID et RM_ApertureID avec la même valeur que RM_UID. Ensuite, lorsque vous mettez fin à la salle à une date et que vous la rétablissez avec une nouvelle plage de dates, RM_UID est newid () et le RM_ApertureID de l'entrée précédente devient le nouveau RM_ApertureID.

Donc, si c'est le cas, RM_ApertureID est un champ non unique et vous ne pouvez donc pas définir de clé étrangère dans une autre table.

Et il n'y a aucun moyen de définir une clé étrangère sur une colonne / un index non unique, par exemple dans T_ZO_REM_AP_Raum_Reinigung (WHERE RM_UID est en fait RM_ApertureID).
Mais pour interdire les valeurs invalides, vous devez définir une clé étrangère, sinon, le gaspillage de données est le résultat le plus tôt possible ...

Maintenant, ce que vous pouvez faire dans ce cas (à moins de réécrire l'ensemble de l'application) est d'insérer une contrainte CHECK, avec une fonction scalaire vérifiant la présence de la clé:

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
la source
Toujours en retard à la fête ... Mais merci pour ce conseil du monde réel - j'ai exactement cela - les données de la table secondaire sont versionnées (a une plage de dates en plus d'une clé), et je veux seulement lier la dernière version de ma table primaire ...
Ian
1
Bons conseils du monde réel! Je peux imaginer de nombreux scénarios avec des applications héritées où la "meilleure pratique" n'est pas possible pour une raison ou une autre, et la contrainte de vérification fonctionnerait bien.
ryanwc
2

Les clés primaires doivent toujours être uniques, les clés étrangères doivent autoriser des valeurs non uniques si la table est une relation un-à-plusieurs. Il est parfaitement normal d'utiliser une clé étrangère comme clé primaire si la table est connectée par une relation un-à-un, et non une relation un-à-plusieurs.

Une contrainte FOREIGN KEY ne doit pas être liée uniquement à une contrainte PRIMARY KEY dans une autre table; il peut également être défini pour référencer les colonnes d'une contrainte UNIQUE dans une autre table.

Anzeem SN
la source