Чи має SQL Server метод вибору між унікальним індексом та первинним ключем?
Принаймні, можна направити SqlServer на посилання первинного ключа, коли створюється зовнішній ключ і існують альтернативні обмеження ключів або унікальні індекси на таблиці, на яку посилається.
Якщо на посилання на первинний ключ потрібно посилатись, у визначенні зовнішнього ключа слід вказати лише ім’я таблиці, на яку посилається, а список стовпців, на які посилається, слід опустити:
ALTER TABLE Child
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
-- omit key columns of the referenced table
REFERENCES Parent /*(ParentID)*/;
Детальніше нижче.
Розглянемо наступне налаштування:
CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);
де таблиця TRef
має намір посилатися на таблицю T
.
Для створення референтного обмеження можна використовувати ALTER TABLE
команду з двома альтернативами:
ALTER TABLE TRef
ADD CONSTRAINT FK_TRef_T_1 FOREIGN KEY (tid) REFERENCES T (id);
ALTER TABLE TRef
ADD CONSTRAINT FK_TRef_T_2 FOREIGN KEY (tid) REFERENCES T;
зауважте, що у другому випадку не вказані стовпці таблиці, на яку посилається, ( REFERENCES T
проти REFERENCES T (id)
).
Оскільки ключових індексів T
ще немає, виконання цих команд призведе до помилок.
Перша команда повертає таку помилку:
Msg 1776, рівень 16, стан 0, рядок 4
У посилальній таблиці "T" немає первинних або кандидатських ключів, які б відповідали списку стовпців, що посилаються, в іноземному ключі "FK_TRef_T_1".
Однак друга команда повертає різні помилки:
Msg 1773, рівень 16, стан 0, рядок 4
Зовнішній ключ 'FK_TRef_T_2' має неявне посилання на об’єкт 'T', у якому не визначений первинний ключ .
дивіться, що в першому випадку очікування є первинним або ключовим ключем , тоді як у другому випадку очікування є первинним ключем .
Давайте перевіримо, чи використовує SqlServer щось інше, ніж первинний ключ, з другою командою чи ні.
Якщо ми додамо деякі унікальні індекси та унікальний ключ на T
:
CREATE UNIQUE INDEX IX_T_1 on T(id) INCLUDE (filler);
CREATE UNIQUE INDEX IX_T_2 on T(id) INCLUDE (c);
CREATE UNIQUE INDEX IX_T_3 ON T(id) INCLUDE (a, b);
ALTER TABLE T
ADD CONSTRAINT UQ_T UNIQUE CLUSTERED (id);
команда для FK_TRef_T_1
створення є успішною, але команда для FK_TRef_T_2
створення все ж не вдається з Msg 1773.
Нарешті, якщо ми додамо первинний ключ T
:
ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);
команда для FK_TRef_T_2
створення вдається.
Давайте перевіримо, на які індекси таблиці T
посилаються зовнішні ключі таблиці TRef
:
select
ix.index_id,
ix.name as index_name,
ix.type_desc as index_type_desc,
fk.name as fk_name
from sys.indexes ix
left join sys.foreign_keys fk on
fk.referenced_object_id = ix.object_id
and fk.key_index_id = ix.index_id
and fk.parent_object_id = object_id('TRef')
where ix.object_id = object_id('T');
це повертає:
index_id index_name index_type_desc fk_name
--------- ----------- ----------------- ------------
1 UQ_T CLUSTERED NULL
2 IX_T_1 NONCLUSTERED FK_TRef_T_1
3 IX_T_2 NONCLUSTERED NULL
4 IX_T_3 NONCLUSTERED NULL
5 PK_T NONCLUSTERED FK_TRef_T_2
див., що FK_TRef_T_2
відповідають PK_T
.
Отже, так, із використанням REFERENCES T
синтаксису зовнішній ключ TRef
відмічається в первинний ключ T
.
Я не зміг знайти таку поведінку, описану в документації SqlServer безпосередньо, але присвячений Msg 1773 говорить про те, що це не випадково. Ймовірно, така реалізація забезпечує відповідність стандарту SQL, нижче - короткий уривок з розділу 11.8 ANSI / ISO 9075-2: 2003
11 Визначення схеми та маніпулювання
11.8 <референтне визначення обмеження>
Функція
Вкажіть референтне обмеження.
Формат
<referential constraint definition> ::=
FOREIGN KEY <left paren> <referencing columns> <right paren>
<references specification>
<references specification> ::=
REFERENCES <referenced table and columns>
[ MATCH <match type> ]
[ <referential triggered action> ]
...
Правила синтаксису
...
3) Випадок:
...
b) Якщо <посилається таблиця та стовпці> не вказує <список посилальних стовпців>, дескриптор таблиці згаданої таблиці повинен містити унікальне обмеження, яке визначає ПЕРШИЙ КЛЮЧ. Нехай посилаються стовпці - це стовпець або стовпці, ідентифіковані унікальними стовпцями в цьому унікальному обмеженні, і посилається стовпець
- один такий стовпець. Вважається, що <посилання на таблицю та стовпці> неявно вказує <список посилальних стовпців>, ідентичний тому <унікальний список стовпців>.
...
Transact-SQL підтримує та розширює ANSI SQL. Однак він точно не відповідає стандарту SQL. Існує документ під назвою Документ про підтримку стандартів SQL Server Transact-SQL ISO / IEC 9075-2 (коротко, див. Тут MS-TSQLISO02 ), що описує рівень підтримки, який надається Transact-SQL. Документ перераховує розширення та варіанти до стандартних. Наприклад, це документи, що MATCH
пункт не підтримується у визначенні референтного обмеження. Але немає жодних документально підтверджених варіантів, що стосуються цитованого стандарту. Отже, моя думка, що спостережувана поведінка є достатньо задокументованою.
І при використанні REFERENCES T (<reference column list>)
синтаксису здається, що SqlServer вибирає перший підходящий некластеризований індекс серед індексів таблиці, на яку посилається (той, що має найменший зовнішній index_id
вигляд, а не той, що має найменший фізичний розмір, як передбачається в коментарях до запитання), або кластерний індекс, якщо він підходить і немає відповідних некластеризованих індексів. Така поведінка, здається, узгоджується з SqlServer 2008 (версія 10.0). Це лише спостереження, звичайно, жодних гарантій у цьому випадку.