Внешний ключ к не первичному ключу


136

У меня есть таблица, которая содержит данные, и одна из этих строк должна существовать в другой таблице. Итак, я хочу, чтобы внешний ключ поддерживал ссылочную целостность.

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

Однако, как видите, в таблице, к которой я добавляю внешний ключ, столбец не является PK. Есть ли способ создать этот внешний ключ или, возможно, лучший способ сохранить эту ссылочную целостность?


В этом нет особого смысла. Почему бы не обратиться к table1.ID?
zerkms

безусловно, если ваш AnothidID не является первичным ключом, это должен быть ForeignKey, поэтому, будучи ForeignKey, ваш table2 должен указывать на одну и ту же таблицу (возможно table3)
Роджер Баррето

Ответы:


182

Если вы действительно хотите создать внешний ключ для не-первичного ключа, это ДОЛЖЕН быть столбец с уникальным ограничением.

Из книг онлайн :

Ограничение FOREIGN KEY не обязательно должно быть связано только с ограничением PRIMARY KEY в другой таблице; он также может быть определен для ссылки на столбцы ограничения UNIQUE в другой таблице.

Так что в вашем случае, если вы сделаете AnotherIDуникальным, это будет разрешено. Если вы не можете применить уникальное ограничение, вам не повезло, но это действительно имеет смысл, если вы думаете об этом.

Хотя, как уже упоминалось, если у вас есть очень хороший первичный ключ в качестве ключа-кандидата, почему бы не использовать его?


1
По поводу вашего последнего вопроса ... У меня есть ситуация, когда я хочу, чтобы составные ключи-кандидаты были первичным ключом только потому, что он семантически имеет большее значение и лучше всего описывает мою модель. Я также хотел бы, чтобы внешний ключ ссылался на недавно созданный суррогатный ключ для производительности (как отмечено выше). Кто-нибудь предвидит проблемы с такой настройкой?
Даниэль Макиас

Сэр, скажите, пожалуйста, в чем логика того, что внешний ключ всегда ссылается на атрибут с уникальным ограничением?
Шиванги Гупта

Как это сделать в asp net MVC 5
irfandar

Может ли обычное целое число не первичного ключа быть объявлено внешним ключом в другой таблице? Как этот. Это возможно? Проект CREATE TABLE (PSLNO Numeric (8,0) Не Null, PrMan Numeric (8,0), StEng Numeric (8,0), CONSTRAINT PK_Project PRIMARY KEY (PSLNO), CONSTRAINT FK_Project1 ИНОСТРАННЫЙ КЛЮЧ (PrMan) ССЫЛКИ Сотрудник (EmpID) , CONSTRAINT FK_Project2 ИНОСТРАННЫЙ КЛЮЧ (StEng) ССЫЛКИ Сотрудник (EmpID),)
Набид

19

Как указывали другие, в идеале внешний ключ должен создаваться как ссылка на первичный ключ (обычно столбец IDENTITY). Однако мы не живем в идеальном мире, и иногда даже «небольшие» изменения в схеме могут оказывать существенное влияние на логику приложения.

Рассмотрим случай таблицы Customer со столбцом SSN (и немым первичным ключом) и таблицы требований, которая также содержит столбец SSN (заполняемый бизнес-логикой из данных Customer, но FK не существует). Дизайн имеет недостатки, но использовался в течение нескольких лет, и три различных приложения были построены на схеме. Должно быть очевидно, что удаление Claim.SSN и установление реальных отношений между PK и FK было бы идеальным, но также и существенным капитальным ремонтом. С другой стороны, наложение уникального ограничения на Customer.SSN и добавление FK на Claim.SSN может обеспечить ссылочную целостность, практически не влияя на приложения.

Не поймите меня неправильно, я за нормализацию, но иногда прагматизм побеждает идеализм. Если посредственный дизайн можно помочь с помощью пластыря, хирургии можно избежать.


18

Necromancing.
Я предполагаю, что когда кто-то попадает сюда, ему нужен внешний ключ для столбца в таблице, содержащей неуникальные ключи.

Проблема в том, что если у вас есть эта проблема, схема базы данных денормализуется.

Например, вы храните комнаты в таблице с первичным ключом room-uid, полем DateFrom и DateTo и другим идентификатором, здесь RM_ApertureID для отслеживания той же комнаты и поле мягкого удаления, например RM_Status, где 99 означает «удаленный», а <> 99 означает «активный».

Поэтому, когда вы создаете первую комнату, вы вставляете RM_UID и RM_ApertureID в качестве того же значения, что и RM_UID. Затем, когда вы заканчиваете комнату на определенную дату и восстанавливаете ее с новым диапазоном дат, RM_UID является newid (), и RM_ApertureID из предыдущей записи становится новым RM_ApertureID.

Так что, если это так, RM_ApertureID является неуникальным полем, и поэтому вы не можете установить внешний ключ в другой таблице.

И нет никакого способа установить внешний ключ для неуникального столбца / индекса, например, в T_ZO_REM_AP_Raum_Reinigung (WHERE RM_UID на самом деле является RM_ApertureID).
Но чтобы запретить недопустимые значения, вам нужно установить внешний ключ, иначе, мусор данных будет результатом скорее, чем позже ...

Теперь, что вы можете сделать в этом случае (если не считать переписывания всего приложения), вставить CHECK-ограничение с помощью скалярной функции, проверяющей наличие ключа:

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

Всегда опаздываю на вечеринку ... Но спасибо за этот реальный совет - у меня именно это есть - данные во вторичной таблице версионированы (имеет диапазон дат в дополнение к ключу), и я только хочу связать последнюю версию из моего основного стола ...
Ян

1
Хороший реальный совет! Я могу представить множество сценариев с унаследованными приложениями, в которых «лучшая практика» невозможна по той или иной причине, и ограничение проверки будет работать хорошо.
ryanwc

Это решение ненадежно. Смотрите: dba.stackexchange.com/…/how-are-my-sql-server-constraints-being-bypassed
stomy

2

Первичные ключи всегда должны быть уникальными, внешние ключи должны разрешать неуникальные значения, если таблица является отношением «один ко многим». Совершенно нормально использовать внешний ключ в качестве первичного ключа, если таблица связана отношением один к одному, а не отношением один ко многим.

Ограничение FOREIGN KEY не обязательно должно быть связано только с ограничением PRIMARY KEY в другой таблице; он также может быть определен для ссылки на столбцы ограничения UNIQUE в другой таблице.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.