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.