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 TRef
intende fare riferimento alla tabella T
.
Per creare un vincolo referenziale si può usare il ALTER TABLE
comando 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 T
rispetto 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_1
creazione ha FK_TRef_T_2
esito 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_2
creazione ha esito positivo.
Controlliamo a quali indici della tabella T
fanno 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_2
corrispondono a PK_T
.
Quindi, sì, con l'uso della REFERENCES T
sintassi 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 MATCH
clausola 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_id
apparentemente, 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.