Есть ли в 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
, выполнение этих команд приведет к ошибкам.
Первая команда возвращает следующую ошибку:
Сообщение 1776, уровень 16, состояние 0, строка 4
В ссылочной таблице «T» нет первичных ключей или ключей-кандидатов, которые соответствуют списку ссылочных столбцов во внешнем ключе «FK_TRef_T_1».
Вторая команда, однако, возвращает другую ошибку:
Сообщение 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) Если <ссылочная таблица и столбцы> не указывает <ссылочный список столбцов>, то дескриптор таблицы ссылочной таблицы должен включать уникальное ограничение, которое задает PRIMARY KEY. Пусть ссылочные столбцы будут столбцом или столбцами, идентифицированными уникальными столбцами в этом уникальном ограничении, и пусть ссылочный столбец
будет одним из таких столбцов. <Ссылочная таблица и столбцы> должны рассматриваться как неявно указывающие <список ссылочных столбцов>, который идентичен этому <уникальному списку столбцов>.
...
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). Это просто наблюдение, конечно, никаких гарантий в этом случае.