¿SQL Server tiene un método para elegir entre un índice único y una clave primaria?
Al menos es posible dirigir SqlServer para que haga referencia a la clave primaria, cuando se crea una clave externa y existen restricciones de clave alternativas o índices únicos en la tabla a la que se hace referencia.
Si es necesario hacer referencia a la clave primaria, solo se debe especificar el nombre de la tabla a la que se hace referencia en la definición de clave externa y se debe omitir la lista de columnas a las que se hace referencia:
ALTER TABLE Child
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
-- omit key columns of the referenced table
REFERENCES Parent /*(ParentID)*/;
Más detalles a continuación.
Considere la siguiente configuración:
CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);
donde tabla TRef
pretende hacer referencia a la tabla T
.
Para crear una restricción referencial, se puede usar el ALTER TABLE
comando con dos alternativas:
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;
observe que en el segundo caso no se especifican columnas de la tabla a la que se hace referencia ( REFERENCES T
versus REFERENCES T (id)
).
Como todavía no hay índices clave activados T
, la ejecución de estos comandos generará errores.
El primer comando devuelve el siguiente error:
Mensaje 1776, Nivel 16, Estado 0, Línea 4
No hay claves principales o candidatas en la tabla referenciada 'T' que coincidan con la lista de columnas de referencia en la clave externa 'FK_TRef_T_1'.
El segundo comando, sin embargo, devuelve un error diferente:
Mensaje 1773, Nivel 16, Estado 0, Línea 4
La clave externa 'FK_TRef_T_2' tiene una referencia implícita al objeto 'T' que no tiene una clave primaria definida.
vea que en el primer caso la expectativa es clave primaria o candidata , mientras que en el segundo caso la expectativa es clave primaria solamente.
Verifiquemos si SqlServer usará algo diferente a la clave primaria con el segundo comando o no.
Si agregamos algunos índices únicos y una clave única en 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);
el comando para la FK_TRef_T_1
creación tiene éxito, pero el comando para la FK_TRef_T_2
creación todavía falla con Msg 1773.
Finalmente, si agregamos clave primaria en T
:
ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);
comando para la FK_TRef_T_2
creación tiene éxito.
Vamos a ver qué índices de la tabla T
hacen referencia las claves externas de la tabla 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');
esto devuelve:
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
ver que FK_TRef_T_2
corresponde a PK_T
.
Entonces, sí, con el uso de la REFERENCES T
sintaxis, la clave externa de TRef
se asigna a la clave primaria de T
.
No pude encontrar el comportamiento descrito en la documentación de SqlServer directamente, pero Msg 1773 dedicado sugiere que no es accidental. Es probable que dicha implementación cumpla con el Estándar SQL, a continuación se incluye un breve extracto de la sección 11.8 de ANSI / ISO 9075-2: 2003
11 Definición y manipulación de esquemas
11.8 <definición de restricción referencial>
Función
Especifica una restricción referencial.
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> ]
...
Reglas de sintaxis
...
3) Caso:
...
b) Si la <tabla y columnas referenciadas> no especifica una <lista de columnas de referencia>, entonces el descriptor de la tabla referenciada incluirá una restricción única que especifica la CLAVE PRIMARIA. Deje que las columnas de referencia sean la columna o columnas identificadas por las columnas únicas en esa restricción única y deje que la columna de referencia
sea una de esas columnas. Se considerará que la <tabla y columnas referenciadas> especifica implícitamente una <lista de columnas de referencia> que es idéntica a esa <lista de columnas única>.
...
Transact-SQL admite y extiende ANSI SQL. Sin embargo, no se ajusta exactamente al estándar SQL. Existe un documento denominado Documento de soporte de estándares ISO / IEC 9075-2 de SQL Server Transact-SQL (MS-TSQLISO02 en resumen, consulte aquí ) que describe el nivel de soporte que proporciona Transact-SQL. El documento enumera extensiones y variaciones al estándar. Por ejemplo, documenta que la MATCH
cláusula no se admite en la definición de restricción referencial. Pero no hay variaciones documentadas relevantes para la norma mencionada. Entonces, mi opinión es que el comportamiento observado está suficientemente documentado.
Y con el uso de la REFERENCES T (<reference column list>)
sintaxis, parece que SqlServer selecciona el primer índice no agrupado adecuado entre los índices de la tabla a la que se hace referencia (el que tiene la menor cantidad index_id
aparente, no el que tiene el tamaño físico más pequeño como se supone en los comentarios de la pregunta), o el índice agrupado si es trajes y no hay índices no agrupados adecuados. Tal comportamiento parece ser consistente desde SqlServer 2008 (versión 10.0). Esto es solo observación, por supuesto, no hay garantías en este caso.