SQL Server a-t-il une méthode pour choisir entre un index unique et une clé primaire?
Au moins, il est possible de diriger SqlServer pour référencer la clé primaire, lorsque la clé étrangère est créée et que des contraintes de clé alternatives ou des index uniques existent sur la table référencée.
Si la clé primaire doit être référencée, seul le nom de la table référencée doit être spécifié dans la définition de la clé étrangère et la liste des colonnes référencées doit être omise:
ALTER TABLE Child
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
-- omit key columns of the referenced table
REFERENCES Parent /*(ParentID)*/;
Plus de détails ci-dessous.
Considérez la configuration suivante:
CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);
où table TRef
entend référencer la table T
.
Pour créer une contrainte référentielle, on peut utiliser la ALTER TABLE
commande avec deux alternatives:
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;
notez que dans le second cas, aucune colonne de la table référencée n'est spécifiée ( REFERENCES T
par rapport à REFERENCES T (id)
).
Puisqu'il n'y a pas T
encore d' index clés sur , l'exécution de ces commandes générera des erreurs.
La première commande renvoie l'erreur suivante:
Msg 1776, niveau 16, état 0, ligne 4
Il n'y a pas de clé primaire ou candidate dans la table référencée 'T' qui correspond à la liste des colonnes de référence dans la clé étrangère 'FK_TRef_T_1'.
La deuxième commande, cependant, renvoie une erreur différente:
Msg 1773, niveau 16, état 0, ligne 4
La clé étrangère 'FK_TRef_T_2' a une référence implicite à l'objet 'T' qui n'a pas de clé primaire définie dessus.
voir que dans le premier cas, l'attente est la clé primaire ou candidate , tandis que dans le second cas, l'attente est la clé primaire uniquement.
Vérifions si SqlServer utilisera autre chose que la clé primaire avec la deuxième commande ou non.
Si nous ajoutons des index uniques et une clé unique sur 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);
commande de FK_TRef_T_1
création réussit, mais la commande de FK_TRef_T_2
création échoue toujours avec Msg 1773.
Enfin, si nous ajoutons la clé primaire sur T
:
ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);
commande de FK_TRef_T_2
création réussit.
Vérifions quels index de la table T
sont référencés par les clés étrangères de la table 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');
cela renvoie:
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
voir qui FK_TRef_T_2
correspondent à PK_T
.
Donc, oui, avec l'utilisation de la REFERENCES T
syntaxe, la clé étrangère de TRef
est mappée à la clé primaire de T
.
Je n'ai pas pu trouver un tel comportement décrit directement dans la documentation SqlServer, mais le Msg dédié 1773 suggère que ce n'est pas accidentel. Une telle implémentation est probablement conforme à la norme SQL, voici un court extrait de la section 11.8 de la norme ANSI / ISO 9075-2: 2003
11 Définition et manipulation de schéma
11.8 <définition de la contrainte référentielle>
Fonction
Spécifiez une contrainte référentielle.
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> ]
...
Règles de syntaxe
...
3) Cas:
...
b) Si la <table et colonnes référencées> ne spécifie pas une <liste de colonnes de référence>, le descripteur de table de la table référencée doit inclure une contrainte unique qui spécifie PRIMARY KEY. Laissez les colonnes référencées être la ou les colonnes identifiées par les colonnes uniques dans cette contrainte unique et laissez la colonne référencée
être une de ces colonnes. La <table et colonnes référencées> doit être considérée comme spécifiant implicitement une <liste de colonnes de référence> qui est identique à cette <liste de colonnes unique>.
...
Transact-SQL prend en charge et étend ANSI SQL. Cependant, il n'est pas exactement conforme à la norme SQL. Il existe un document nommé SQL Server Transact-SQL ISO / IEC 9075-2 Standards Support Document (MS-TSQLISO02 en bref, voir ici ) décrivant le niveau de prise en charge fourni par Transact-SQL. Le document répertorie les extensions et les variations de la norme. Par exemple, il documente que la MATCH
clause n'est pas prise en charge dans la définition de contrainte référentielle. Mais il n'y a pas de variations documentées pertinentes pour l'élément de norme cité. Donc, mon opinion est que le comportement observé est suffisamment documenté.
Et avec l'utilisation de la REFERENCES T (<reference column list>)
syntaxe, il semble que SqlServer sélectionne le premier index non cluster approprié parmi les index de la table référencée (celui avec le moins index_id
apparemment, pas celui avec la plus petite taille physique comme supposé dans les commentaires de la question), ou un index cluster si c'est le cas convient et il n'y a pas d'index non cluster adaptés. Un tel comportement semble cohérent depuis SqlServer 2008 (version 10.0). Ceci est juste une observation bien sûr, aucune garantie dans ce cas.