Clave externa a clave no primaria

136

Tengo una tabla que contiene datos, y una de esas filas debe existir en otra tabla. Por lo tanto, quiero una clave externa para mantener la integridad referencial.

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

Sin embargo, como puede ver, la tabla a la que formo una clave externa, la columna no es la PK. ¿Hay alguna forma de crear esta clave externa, o tal vez una mejor manera de mantener esta integridad referencial?

Craig
fuente
No tiene mucho sentido hacer eso. ¿Por qué no te refieres table1.ID?
zerkms
es definitivo que si su AnothidID no es una clave principal, debería ser una ForeignKey, por lo que al ser una ForeignKey, su tabla2 debería apuntar a la misma tabla (posible tabla3)
Roger Barreto

Respuestas:

182

Si realmente desea crear una clave foránea para una clave no primaria, DEBE ser una columna que tenga una restricción única.

De libros en línea :

Una restricción FOREIGN KEY no tiene que estar vinculada solo a una restricción PRIMARY KEY en otra tabla; También se puede definir para hacer referencia a las columnas de una restricción ÚNICA en otra tabla.

Entonces, en su caso, si lo hace AnotherIDúnico, se permitirá. Si no puede aplicar una restricción única, no tiene suerte, pero esto realmente tiene sentido si lo piensa.

Aunque, como se ha mencionado, si tiene una clave primaria perfectamente buena como clave candidata, ¿por qué no usarla?

Ian Preston
fuente
1
Relacionado con su última pregunta ... Tengo una situación en la que me gustaría que las claves candidatas compuestas sean la clave principal solo porque semánticamente tiene más importancia y describe mejor mi modelo. También me gustaría que una clave externa haga referencia a una clave sustituta recién creada por el bien del rendimiento (como se señaló anteriormente). ¿Alguien prevé algún problema con tal configuración?
Daniel Macias
Señor, ¿puede decir cuál es la lógica detrás de que la clave externa siempre hace referencia al atributo con una restricción única?
Shivangi Gupta
Cómo hacer esto en asp net MVC 5
irfandar
¿Se puede declarar el número entero de clave no primaria normal como clave foránea en otra tabla? Como éste. ¿es posible? Proyecto 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

Como otros han señalado, idealmente, la clave externa se crearía como referencia a una clave primaria (generalmente una columna IDENTIDAD). Sin embargo, no vivimos en un mundo ideal y, a veces, incluso un cambio "pequeño" en un esquema puede tener efectos significativos en la lógica de la aplicación.

Considere el caso de una tabla de Cliente con una columna de SSN (y una clave primaria tonta), y una tabla de Reclamación que también contiene una columna de SSN (poblada por la lógica de negocios de los datos del Cliente, pero no existe FK). El diseño es defectuoso, pero ha estado en uso durante varios años, y se han construido tres aplicaciones diferentes en el esquema. Debería ser obvio que eliminar a Claim.SSN y establecer una verdadera relación PK-FK sería ideal, pero también sería una revisión importante . Por otro lado, poner una restricción ÚNICA en Customer.SSN y agregar un FK en Claim.SSN, podría proporcionar integridad referencial, con poco o ningún impacto en las aplicaciones.

No me malinterpreten, estoy a favor de la normalización, pero a veces el pragmatismo vence al idealismo. Si se puede ayudar a un diseño mediocre con una curita, se podría evitar la cirugía.

EJSawyer
fuente
18

Nigromancia
Supongo que cuando alguien aterriza aquí, necesita una clave externa para la columna en una tabla que contiene claves no únicas.

El problema es que si tiene ese problema, el esquema de la base de datos se desnormaliza.

Por ejemplo, mantiene las salas en una tabla, con una clave principal de room-uid, un campo DateFrom y DateTo, y otro uid, aquí RM_ApertureID para realizar un seguimiento de la misma sala y un campo de borrado suave, como RM_Status, donde 99 significa 'eliminado' y <> 99 significa 'activo'.

Entonces, cuando crea la primera sala, inserta RM_UID y RM_ApertureID como el mismo valor que RM_UID. Luego, cuando finaliza la sala a una fecha y la restablece con un nuevo rango de fechas, RM_UID es newid (), y el RM_ApertureID de la entrada anterior se convierte en el nuevo RM_ApertureID.

Entonces, si ese es el caso, RM_ApertureID es un campo no único, por lo que no puede establecer una clave externa en otra tabla.

Y no hay forma de establecer una clave externa para una columna / índice no único, por ejemplo, en T_ZO_REM_AP_Raum_Reinigung (DONDE RM_UID es realmente RM_ApertureID).
Pero para prohibir los valores no válidos, debe establecer una clave foránea, de lo contrario, la basura de datos es el resultado más temprano que tarde ...

Ahora, lo que puede hacer en este caso (salvo reescribir toda la aplicación) es insertar una restricción CHECK, con una función escalar que verifica la presencia de la clave:

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
fuente
Siempre llega tarde a la fiesta ... Pero gracias por este consejo del mundo real, tengo exactamente eso, los datos en la tabla secundaria están versionados (tiene un rango de fechas además de una clave), y solo quiero vincular la última versión desde mi mesa principal ...
Ian
1
Buen consejo del mundo real! Puedo imaginar muchos escenarios con aplicaciones heredadas donde la "mejor práctica" no es posible por una razón u otra, y la restricción de verificación funcionaría bien.
ryanwc
2

Las claves primarias siempre deben ser únicas, las claves externas deben permitir valores no únicos si la tabla es una relación de uno a muchos. Está perfectamente bien usar una clave externa como clave principal si la tabla está conectada por una relación uno a uno, no una relación uno a muchos.

Una restricción FOREIGN KEY no tiene que estar vinculada solo a una restricción PRIMARY KEY en otra tabla; También se puede definir para hacer referencia a las columnas de una restricción ÚNICA en otra tabla.

Anzeem SN
fuente