In che modo SQL Server sceglie una chiave di indice per un riferimento di chiave esterna?


9

Sto lavorando con un database legacy che è stato importato da MS Access. Esistono una ventina di tabelle con chiavi primarie uniche non cluster, create durante l'aggiornamento di MS Access> SQL Server.

Molte di queste tabelle hanno anche indici univoci, non cluster, che sono duplicati della chiave primaria.

Sto tentando di ripulirlo.

Ma quello che ho trovato è dopo che ho ricreato le chiavi primarie come indici cluster, e quindi ho provato a ricostruire la chiave esterna, la chiave esterna fa riferimento al vecchio indice duplicato (che era unico).

Lo so perché non mi lascerà cadere gli indici duplicati.

Penso che SQL Server sceglierebbe sempre una chiave primaria se esistesse. SQL Server ha un metodo per scegliere tra un indice univoco e una chiave primaria?

Per duplicare il problema (su 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 

Messaggio di errore:

Messaggio 3723, livello 16, stato 6, riga 36 Un indice DROP esplicito non è consentito sull'indice "Parent.IX_Parent". Viene utilizzato per l'applicazione del vincolo ESTERO KEY.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White 9

Risposte:


7

La (mancanza di) documentazione suggerisce che questo comportamento è un dettaglio di implementazione ed è quindi indefinito e soggetto a modifiche in qualsiasi momento.

Ciò è in netto contrasto con CREATE FULLTEXT INDEX , in cui è necessario specificare il nome di un indice a cui collegarsi - AFAIK, non esiste una FOREIGN KEYsintassi non documentata per fare l'equivalente (anche se teoricamente, potrebbe esserci in futuro).

Come accennato, ha senso che SQL Server scelga l'indice fisico più piccolo con cui associare la chiave esterna. Se si modifica lo script per creare il vincolo univoco come CLUSTERED, lo script "funziona" su 2008 R2. Ma quel comportamento è ancora indefinito e non dovrebbe essere invocato.

Come con la maggior parte delle applicazioni legacy, dovrai solo andare alle cose nitide e pulite.


"SQL Server sceglie l'indice fisico più piccolo con cui associare la chiave esterna" non necessariamente in realtà. C'è un esempio nella risposta vicina in cui SqlServer sceglie un indice che non ha dimensioni fisiche minime.
I-ONE

3

SQL Server ha un metodo per scegliere tra un indice univoco e una chiave primaria?

Almeno è possibile indirizzare SqlServer a fare riferimento alla chiave primaria, quando viene creata una chiave esterna e sulla tabella a cui si fa riferimento esistono vincoli di chiave alternativi o indici univoci.

Se è necessario fare riferimento alla chiave primaria, è necessario specificare solo il nome della tabella a cui si fa riferimento nella definizione della chiave esterna e l'elenco delle colonne a cui si fa riferimento deve essere omesso:

ALTER TABLE Child
    ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
        -- omit key columns of the referenced table
        REFERENCES Parent /*(ParentID)*/;

Maggiori dettagli di seguito.


Considera la seguente configurazione:

CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);

dove tabella TRefintende fare riferimento alla tabella T.

Per creare un vincolo referenziale si può usare il ALTER TABLEcomando con due alternative:

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;

si noti che nel secondo caso non sono specificate colonne della tabella a cui si fa riferimento ( REFERENCES Trispetto REFERENCES T (id)).

Poiché non ci sono ancora indici chiave attivi T, l'esecuzione di questi comandi genererà errori.

Il primo comando restituisce il seguente errore:

Messaggio 1776, livello 16, stato 0, riga 4

Non ci sono chiavi primarie o candidate nella tabella referenziata 'T' che corrispondono all'elenco delle colonne di riferimento nella chiave esterna 'FK_TRef_T_1'.

Il secondo comando, tuttavia, restituisce un errore diverso:

Messaggio 1773, livello 16, stato 0, riga 4

La chiave esterna 'FK_TRef_T_2' ha un riferimento implicito all'oggetto 'T' che non ha una chiave primaria definita su di esso.

vedere che nel primo caso l'aspettativa è la chiave primaria o candidata , mentre nel secondo caso l'aspettativa è solo la chiave primaria .

Controlliamo se SqlServer utilizzerà o meno qualcosa di diverso dalla chiave primaria con il secondo comando.

Se aggiungiamo alcuni indici univoci e una chiave univoca su 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);

il comando per la FK_TRef_T_1creazione ha FK_TRef_T_2esito positivo, ma il comando per la creazione non riesce ancora con il messaggio 1773.

Infine, se aggiungiamo la chiave primaria su T:

ALTER TABLE T
    ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);

il comando per la FK_TRef_T_2creazione ha esito positivo.

Controlliamo a quali indici della tabella Tfanno riferimento le chiavi esterne della tabella 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');

questo ritorna:

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

vedere che FK_TRef_T_2corrispondono a PK_T.

Quindi, sì, con l'uso della REFERENCES Tsintassi la chiave esterna di TRefè mappata sulla chiave primaria di T.

Non sono stato in grado di trovare direttamente tale comportamento descritto nella documentazione di SqlServer, ma il messaggio 1773 dedicato suggerisce che non è casuale. Probabilmente tale implementazione fornisce la conformità allo standard SQL, di seguito è riportato un breve estratto dalla sezione 11.8 di ANSI / ISO 9075-2: 2003

11 Definizione e manipolazione dello schema

11,8 <definizione del vincolo referenziale>

Funzione
Specifica un vincolo referenziale.

Formato

<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> ]
...

Regole di sintassi
...
3) Caso:
...
b) Se la <tabella e colonne di riferimento> non specifica un <elenco di colonne di riferimento>, il descrittore di tabella della tabella di riferimento deve includere un vincolo univoco che specifica PRIMARY KEY. Consenti alle colonne di riferimento di essere la colonna o le colonne identificate dalle colonne univoche in quel vincolo univoco e lasciare che la colonna di riferimento sia una di tali colonne. La <tabella e colonne di riferimento> deve essere considerata in modo implicito per specificare un <elenco di colonne di riferimento> identico a quello <elenco di colonne univoco>.
...

Transact-SQL supporta ed estende ANSI SQL. Tuttavia, non è esattamente conforme allo standard SQL. Esiste un documento denominato Documento di supporto agli standard Transact-SQL ISO / IEC 9075-2 di SQL Server (in breve MS-TSQLISO02, vedere qui ) che descrive il livello di supporto fornito da Transact-SQL. Il documento elenca estensioni e variazioni allo standard. Ad esempio, documenta che la MATCHclausola non è supportata nella definizione del vincolo referenziale. Ma non ci sono variazioni documentate relative al pezzo di standard citato. Quindi, la mia opinione è che il comportamento osservato sia sufficientemente documentato.

E con l'uso della REFERENCES T (<reference column list>)sintassi sembra che SqlServer selezioni il primo indice non cluster adatto tra gli indici della tabella a cui si fa riferimento (quello con il meno index_idapparentemente, non quello con la dimensione fisica più piccola come ipotizzato nei commenti alla domanda), o indice cluster se semi e non ci sono indici non cluster adatti. Tale comportamento sembra essere coerente da SqlServer 2008 (versione 10.0). Questa è solo osservazione ovviamente, nessuna garanzia in questo caso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.