Czy SQL Server ma metodę wyboru między unikalnym indeksem a kluczem podstawowym?
Przynajmniej możliwe jest skierowanie SqlServer do referencyjnego klucza podstawowego, gdy tworzony jest klucz obcy i istnieją alternatywne ograniczenia klucza lub unikalne indeksy w tabeli, do której się odwołuje.
Jeśli należy odwoływać się do klucza podstawowego, w definicji klucza obcego należy podać tylko nazwę tabeli, do której następuje odwołanie, a listę odwoływanych kolumn należy pominąć:
ALTER TABLE Child
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
-- omit key columns of the referenced table
REFERENCES Parent /*(ParentID)*/;
Więcej szczegółów poniżej.
Rozważ następującą konfigurację:
CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);
gdzie tabela TRef
zamierza odwoływać się do tabeli T
.
Aby utworzyć ograniczenie referencyjne, można użyć ALTER TABLE
polecenia z dwiema alternatywami:
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;
zauważ, że w drugim przypadku nie podano żadnych kolumn tabeli, do której następuje odwołanie (w REFERENCES T
porównaniu REFERENCES T (id)
).
Ponieważ nie ma jeszcze żadnych indeksów kluczy T
, wykonanie tych poleceń spowoduje wygenerowanie błędów.
Pierwsze polecenie zwraca następujący błąd:
Msg 1776, poziom 16, stan 0, wiersz 4
W tabeli referencyjnej „T” nie ma kluczy podstawowych ani kandydujących, które pasują do listy kolumn referencyjnych w kluczu obcym „FK_TRef_T_1”.
Drugie polecenie zwraca jednak inny błąd:
Msg 1773, poziom 16, stan 0, wiersz 4
Klucz obcy „FK_TRef_T_2” zawiera niejawne odniesienie do obiektu „T”, który nie ma zdefiniowanego klucza podstawowego .
zobacz, że w pierwszym przypadku oczekiwanie jest kluczem podstawowym lub kluczem kandydującym , podczas gdy w drugim przypadku oczekiwanie jest tylko kluczem podstawowym .
Sprawdźmy, czy SqlServer użyje czegoś innego niż klucz podstawowy przy drugim poleceniu, czy nie.
Jeśli dodamy kilka unikalnych indeksów i unikalny klucz do 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);
polecenie FK_TRef_T_1
tworzenia powiodło się, ale polecenie FK_TRef_T_2
utworzenia nadal nie działa z Msg 1773.
Wreszcie, jeśli dodamy klucz podstawowy do T
:
ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);
polecenie FK_TRef_T_2
tworzenia powiodło się.
Sprawdźmy, do których indeksów tabeli T
odwołują się obce klucze tabeli 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');
to zwraca:
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
zobacz, że FK_TRef_T_2
odpowiada PK_T
.
Tak, przy użyciu REFERENCES T
składni klucz obcy TRef
jest odwzorowywany na klucz podstawowy z T
.
Nie byłem w stanie znaleźć takiego zachowania opisanego bezpośrednio w dokumentacji SqlServer, ale dedykowany Msg 1773 sugeruje, że nie jest to przypadek. Prawdopodobnie taka implementacja zapewnia zgodność ze standardem SQL, poniżej znajduje się krótki fragment z sekcji 11.8 ANSI / ISO 9075-2: 2003
11 Definicja schematu i manipulacja
11.8 <definicja ograniczenia referencyjnego>
Funkcja
Określ ograniczenie referencyjne.
Format
<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> ]
...
Zasady składni
...
3) Przypadek:
...
b) Jeśli <tabela i kolumny odniesienia> nie określa <listy kolumn odniesienia>, wówczas deskryptor tabeli tabeli odniesienia zawiera unikalne ograniczenie określające KLUCZ PODSTAWOWY. Niech kolumny odniesienia będą kolumnami lub kolumnami identyfikowanymi przez unikalne kolumny w tym unikalnym ograniczeniu i niech kolumna odniesienia
będzie jedną taką kolumną. Uważa się, że <tabela i kolumny odniesienia> domyślnie określają <listę kolumn odniesienia>, która jest identyczna z <listą unikalnych kolumn>.
...
Transact-SQL obsługuje i rozszerza ANSI SQL. Nie jest jednak dokładnie zgodny ze standardem SQL. Istnieje dokument o nazwie SQL Server Transact-SQL Standard ISO / IEC 9075-2 Dokument wsparcia standardów (w skrócie MS-TSQLISO02, patrz tutaj ) opisujący poziom wsparcia zapewnianego przez Transact-SQL. Dokument zawiera listę rozszerzeń i odmian standardu. Na przykład dokumentuje, że MATCH
klauzula nie jest obsługiwana w definicji ograniczenia referencyjnego. Ale nie ma udokumentowanych zmian odnoszących się do cytowanego standardu. Moim zdaniem obserwowane zachowanie jest wystarczająco udokumentowane.
I przy użyciu REFERENCES T (<reference column list>)
składni wydaje się, że SqlServer wybiera pierwszy odpowiedni indeks nieklastrowany spośród indeksów tabeli, do której się odwołuje (ten, który index_id
wydaje się najmniej widoczny, a nie ten o najmniejszym rozmiarze fizycznym, jak zakładano w komentarzach do pytania), lub indeks klastrowany, jeśli pasuje i nie ma odpowiednich indeksów nieklastrowanych. Takie zachowanie wydaje się być spójne od SqlServer 2008 (wersja 10.0). To oczywiście tylko obserwacja, w tym przypadku nie ma gwarancji.