Зовнішній ключ - не первинний ключ


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

Однак, як бачите, таблиця, до якої я зарубіжний ключ, стовпець не є ПК. Чи є спосіб створити цей зовнішній ключ чи, можливо, кращий спосіб зберегти цю референтну цілісність?


Це не має особливого сенсу. Чому б не звернутися table1.ID?
zerkms

остаточно, що якщо ваш AnothidID не є первинним ключем, це повинен бути ForeignKey, тому, будучи ForeignKey, ваша таблиця2 повинна вказувати на ту саму таблицю (можливу таблицю3)
Роджер Баррето,

Відповіді:


182

Якщо ви дійсно хочете створити зовнішній ключ для не первинного ключа, ОБОВ'ЯЗКОВО це стовпець, на якому є унікальне обмеження.

З книг онлайн :

Обмеження ЗОВНІШНЯ КЛЮЧА не повинно бути пов'язане лише з обмеженням ОСНОВНОГО КЛЮЧУ в іншій таблиці; його також можна визначити для посилання на стовпці обмеження UNIQUE в іншій таблиці.

Тож у вашому випадку, якщо ви зробите AnotherIDунікальний, це буде дозволено. Якщо ви не можете застосувати унікальне обмеження, вам не пощастило, але це дійсно має сенс, якщо ви задумаєтесь про це.

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


1
Що стосується вашого останнього запитання ... У мене є ситуація, коли я хотів би, щоб складені ключові кандидати були первинним ключем лише тому, що це семантично має більше значення і найкраще описує мою модель. Я також хотів би мати посилання на зовнішній ключ на новостворений сурогатний ключ задля виконання (як зазначено вище). Хтось передбачає якісь проблеми з таким налаштуванням?
Даніель Макіас

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

Як це зробити в мережі asp net MVC 5
irfandar

Чи може звичайне ціле число первинного ключа оголосити іноземним ключем в іншій таблиці? Як ця. це можливо? Проект СТВОРИТИ ТАБЛИЦЮ (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) РЕФЕРЕНЦІЯ , CONSTRAINT FK_Project2 FOREIGN KEY (StEng) ДОВІДКИ Співробітник (EmpID),)
Nabid

19

Як зазначають інші, в ідеалі зовнішній ключ створюватиметься як посилання на первинний ключ (як правило, стовпець ІДЕНТИЧНОСТІ). Однак ми не живемо в ідеальному світі, і іноді навіть "невелика" зміна схеми може мати суттєві пульсаційні ефекти на логіку програми.

Розглянемо випадок таблиці Клієнта зі стовпцем SSN (та німим первинним ключем) та таблицею претензій, яка також містить стовпчик SSN (заповнений бізнес-логікою з даних Клієнта, але ФК не існує). Дизайн є недосконалим, але він використовується вже кілька років, і на схемі було побудовано три різних програми. Повинно бути очевидним, що зірвати Claim.SSN та встановити реальні стосунки PK-FK було б ідеально, але це також було б значним капітальним ремонтом. З іншого боку, встановлення UNIQUE обмеження на Customer.SSN та додавання FK до Claim.SSN може забезпечити цілісність референції, мало впливаючи на програми або зовсім не впливаючи на них.

Не зрозумійте мене неправильно, я все за нормалізацію, але іноді прагматизм перемагає ідеалізм. Якщо посередньою конструкцією можна допомогти за допомогою пов'язки, можливо уникнути операції.


18

Некромантування.
Я припускаю, що коли хтось тут приземляється, йому потрібен сторонній ключ для стовпця в таблиці, який містить унікальні ключі.

Проблема полягає в тому, що якщо у вас є ця проблема, база даних-схема денормалізована.

Наприклад, ви зберігаєте кімнати в таблиці з первинним ключем номер-uid, полем DateFrom та DateTo та іншим uid, тут 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 (де 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

Первинні ключі завжди повинні бути унікальними, зовнішні ключі повинні допускати не унікальні значення, якщо таблиця стосується один до багатьох. Цілком чудово використовувати зовнішній ключ в якості основного ключа, якщо таблиця з'єднана відносинами один на один, а не відносинами один на багато.

Обмеження ЗОВНІШНЯ КЛЮЧА не повинно бути пов'язане лише з обмеженням ОСНОВНОГО КЛЮЧУ в іншій таблиці; його також можна визначити для посилання на стовпці обмеження UNIQUE в іншій таблиці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.