W jaki sposób SQL Server wybiera klucz indeksu dla odwołania do klucza obcego?

9

Pracuję ze starszą bazą danych, która została zaimportowana z MS Access. Istnieje około dwudziestu tabel z nieklastrowanymi, unikatowymi kluczami głównymi, które zostały utworzone podczas aktualizacji MS Access> SQL Server.

Wiele z tych tabel ma także unikalne, nieklastrowane indeksy, które są duplikatami klucza podstawowego.

Próbuję to oczyścić.

Ale to, co znalazłem, to po tym, jak odtworzyłem klucze podstawowe jako indeksy klastrowe, a następnie próbuję odbudować klucz obcy, klucz obcy odnosi się do starego, zduplikowanego indeksu (który był unikalny).

Wiem o tym, ponieważ nie pozwoli mi upuścić zduplikowanych indeksów.

Myślę, że SQL Server zawsze wybiera klucz podstawowy, jeśli taki istnieje. Czy SQL Server ma metodę wyboru między unikalnym indeksem a kluczem podstawowym?

Aby zduplikować problem (w SQL Server 2008 R2):

IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Child') DROP TABLE Child
GO
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Parent') DROP TABLE Parent
GO

-- Create the parent table
CREATE TABLE Parent (ParentID INT NOT NULL IDENTITY(1,1)) 

-- Make the parent table a heap
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY NONCLUSTERED (ParentID) 

-- Create the duplicate index on the parent table
CREATE UNIQUE NONCLUSTERED INDEX IX_Parent ON Parent (ParentID) 

-- Create the child table
CREATE TABLE Child  (ChildID  INT NOT NULL IDENTITY(1,1), ParentID INT NOT NULL ) 

-- Give the child table a normal PKey
ALTER TABLE Child ADD CONSTRAINT PK_Child PRIMARY KEY CLUSTERED (ChildID) 

-- Create a foreign key relationship with the Parent table on ParentID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to clean this up
-- Drop the foreign key constraint on the Child table
ALTER TABLE Child DROP CONSTRAINT FK_Child

-- Drop the primary key constraint on the Parent table
ALTER TABLE Parent DROP CONSTRAINT PK_Parent

-- Recreate the primary key on Parent as a clustered index
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY CLUSTERED (ParentID) 

-- Recreate the foreign key in Child pointing to parent ID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to drop the duplicate index on Parent 
DROP INDEX IX_Parent ON Parent 

Błąd msg:

Msg 3723, poziom 16, stan 6, wiersz 36 Wyraźny INDEKS DROP nie jest dozwolony w indeksie „Parent.IX_Parent”. Jest używany do wymuszania ograniczeń na klucz OBCY.

8kb
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Paul White 9

Odpowiedzi:

7

(Brak) dokumentacji sugeruje, że takie zachowanie jest szczegółem implementacji, a zatem jest niezdefiniowane i może ulec zmianie w dowolnym momencie.

Stanowi to wyraźny kontrast w stosunku do TWORZENIA PEŁNOTEKSTOWEGO INDEKSU , w którym musisz podać nazwę indeksu, do którego chcesz dołączyć - AFAIK, nie ma nieudokumentowanej FOREIGN KEYskładni, aby wykonać równoważny (choć teoretycznie może być w przyszłości).

Jak wspomniano, ma sens, że SQL Server wybiera najmniejszy indeks fizyczny, z którym ma zostać skojarzony klucz obcy. Jeśli zmienisz skrypt, aby utworzyć unikalne ograniczenie as CLUSTERED, skrypt „zadziała” na 2008 R2. Ale to zachowanie jest nadal nieokreślone i nie należy na nim polegać.

Podobnie jak w przypadku większości starszych aplikacji, musisz po prostu przejść do drobiazgowości i posprzątać.

Jon Seigel
źródło
„SQL Server wybiera najmniejszy indeks fizyczny, z którym ma zostać skojarzony klucz obcy” niekoniecznie tak naprawdę. W sąsiedniej odpowiedzi znajduje się przykład, w którym SqlServer wybiera indeks, który nie ma najmniejszego rozmiaru fizycznego.
i-one
3

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 TRefzamierza odwoływać się do tabeli T.

Aby utworzyć ograniczenie referencyjne, można użyć ALTER TABLEpolecenia 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 Tporó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_1tworzenia powiodło się, ale polecenie FK_TRef_T_2utworzenia 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_2tworzenia powiodło się.

Sprawdźmy, do których indeksów tabeli Todwoł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_2odpowiada PK_T.

Tak, przy użyciu REFERENCES Tskładni klucz obcy TRefjest 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 MATCHklauzula 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_idwydaje 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.

i-one
źródło