Chave estrangeira para chave não primária

136

Eu tenho uma tabela que contém dados e uma dessas linhas precisa existir em outra tabela. Então, eu quero uma chave estrangeira para manter a integridade 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)
)

No entanto, como você pode ver, a chave estrangeira da tabela para a qual a coluna não é o PK. Existe uma maneira de criar essa chave estrangeira ou talvez uma maneira melhor de manter essa integridade referencial?

Craig
fonte
Não faz muito sentido fazer isso. Por que não se referir table1.ID?
Zerkms 26/08/13
é definitiva de que se o seu AnothidID não é uma chave primária deve ser uma ForeignKey, de modo a ser um ForeignKey, seu table2 deveria aponta para a mesma mesa (possível table3)
Roger Barreto

Respostas:

182

Se você realmente deseja criar uma chave estrangeira para uma chave não primária, DEVE ser uma coluna que tenha uma restrição exclusiva.

Dos Livros Online :

Uma restrição FOREIGN KEY não precisa ser vinculada apenas a uma restrição PRIMARY KEY em outra tabela; também pode ser definido para referenciar as colunas de uma restrição UNIQUE em outra tabela.

Portanto, no seu caso, se você criar um modelo AnotherIDexclusivo, será permitido. Se você não pode aplicar uma restrição única, está sem sorte, mas isso realmente faz sentido se você pensar sobre isso.

Embora, como mencionado, se você possui uma chave primária perfeitamente boa como chave candidata, por que não usá-la?

Ian Preston
fonte
1
Relacionada à sua última pergunta ... Eu tenho uma situação em que gostaria que as chaves candidatas compostas fossem a chave primária apenas porque semanticamente tem mais importância e descreve melhor meu modelo. Eu também gostaria que uma referência de chave estrangeira fosse uma chave substituta recém-criada em prol do desempenho (conforme observado acima). Alguém prevê algum problema com essa configuração?
Daniel Macias
Senhor, você pode dizer qual é a lógica por trás dessa chave estrangeira sempre referencia o atributo com restrição única?
Shivangi Gupta 23/08
Como fazer isso no asp net MVC 5
irfandar
Um número inteiro normal de chave não primária pode ser declarado chave estrangeira em outra tabela? Como este. Isso é possível? Projeto CREATE TABLE (PSLNO Numérico (8,0) Não Nulo, PrMan Numérico (8,0), StEng Numérico (8,0), CONSTRAINT PK_Project PRIMARY KEY (PSLNO), CONSTRAINT FK_Project1 FOREIGN KEY (PrMan) REFERÊNCIAS Empregado (EmpID) , CONSTRAINT FK_Project2 FOREIGN KEY (StEng) REFERÊNCIAS Employee (EmpID),)
Nabid
19

Como outros já apontaram, idealmente, a chave estrangeira seria criada como referência a uma chave primária (geralmente uma coluna IDENTITY). No entanto, não vivemos em um mundo ideal e, às vezes, mesmo uma mudança "pequena" em um esquema pode ter efeitos significativos na lógica da aplicação.

Considere o caso de uma tabela Customer com uma coluna SSN (e uma chave primária burra) e uma tabela de declaração que também contém uma coluna SSN (preenchida pela lógica de negócios a partir dos dados do Cliente, mas não existe FK). O design é falho, mas está em uso há vários anos, e três aplicativos diferentes foram criados no esquema. Deveria ser óbvio que extrair o Claim.SSN e estabelecer um relacionamento PK-FK real seria o ideal, mas também seria uma revisão significativa . Por outro lado, colocar uma restrição ÚNICA no Customer.SSN e adicionar um FK no Claim.SSN pode fornecer integridade referencial, com pouco ou nenhum impacto nos aplicativos.

Não me interpretem mal, sou a favor da normalização, mas às vezes o pragmatismo vence o idealismo. Se um desenho medíocre puder ser ajudado com um curativo, a cirurgia poderá ser evitada.

EJSawyer
fonte
18

Necromante.
Suponho que quando alguém chega aqui, ele precisa de uma chave estrangeira para coluna em uma tabela que contém chaves não exclusivas.

O problema é que, se você tiver esse problema, o esquema do banco de dados será desnormalizado.

Você está, por exemplo, mantendo salas em uma tabela, com uma chave primária de espaço em disco, um campo DateFrom e DateTo e outro campo, aqui RM_ApertureID para acompanhar a mesma sala e um campo de exclusão suave, como RM_Status, onde 99 significa 'excluído' e <> 99 significa 'ativo'.

Portanto, quando você cria a primeira sala, insira RM_UID e RM_ApertureID como o mesmo valor que RM_UID. Em seguida, quando você encerra a sala em uma data e a restabelece com um novo período, RM_UID é newid () e o RM_ApertureID da entrada anterior se torna o novo RM_ApertureID.

Portanto, se for esse o caso, RM_ApertureID é um campo não exclusivo e, portanto, você não pode definir uma chave estrangeira em outra tabela.

E não há como definir uma chave estrangeira para uma coluna / índice não exclusivo, por exemplo, em T_ZO_REM_AP_Raum_Reinigung (WHERE RM_UID é realmente RM_ApertureID).
Mas para proibir valores inválidos, é necessário definir uma chave estrangeira, caso contrário, o lixo de dados é o resultado mais cedo ou mais tarde ...

Agora, o que você pode fazer neste caso (exceto reescrever o aplicativo inteiro) é inserir uma restrição CHECK, com uma função escalar que verifica a presença da chave:

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
fonte
Sempre atrasado para a festa ... Mas obrigado por esse conselho do mundo real - eu tenho exatamente isso - os dados na tabela secundária são versionados (tem um intervalo de datas além de uma chave) e eu só quero vincular a versão mais recente da minha mesa principal ...
Ian
1
Um conselho do mundo real! Eu posso imaginar muitos cenários com aplicativos herdados em que a "melhor prática" não é possível por um motivo ou outro, e a restrição de verificação funcionaria bem.
Ryanwc # 6/18
2

As chaves primárias sempre precisam ser exclusivas, as chaves estrangeiras precisam permitir valores não exclusivos se a tabela for um relacionamento um para muitos. É perfeitamente bom usar uma chave estrangeira como chave primária se a tabela estiver conectada por um relacionamento um para um, não um para um para muitos.

Uma restrição FOREIGN KEY não precisa ser vinculada apenas a uma restrição PRIMARY KEY em outra tabela; também pode ser definido para referenciar as colunas de uma restrição UNIQUE em outra tabela.

Anzeem SN
fonte