Estoy trabajando con una base de datos heredada que se importó de MS Access. Hay alrededor de veinte tablas con claves primarias únicas no agrupadas que se crearon durante la actualización de MS Access> SQL Server.
Muchas de estas tablas también tienen índices únicos no agrupados que son duplicados de la clave primaria.
Estoy intentando limpiar esto.
Pero lo que he encontrado es que después de recrear las claves primarias como índices agrupados, y luego intentar reconstruir la clave externa, la clave externa hace referencia al índice antiguo y duplicado (que era único).
Sé esto porque no me deja caer los índices duplicados.
Creo que SQL Server siempre elegiría una clave primaria si existiera. ¿SQL Server tiene un método para elegir entre un índice único y una clave primaria?
Para duplicar el problema (en 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
Mensaje de error:
Msg 3723, Nivel 16, Estado 6, Línea 36 No se permite un ÍNDICE DE GOTA explícito en el índice 'Parent.IX_Parent'. Se está utilizando para la aplicación de restricciones FOREIGN KEY.
Respuestas:
La (falta de) documentación sugiere que este comportamiento es un detalle de implementación y, por lo tanto, no está definido y está sujeto a cambios en cualquier momento.
Esto está en marcado contraste con CREATE FULLTEXT INDEX , donde debe especificar el nombre de un índice para adjuntar: AFAIK, no hay
FOREIGN KEY
sintaxis indocumentada para hacer el equivalente (aunque en teoría, podría existir en el futuro).Como se mencionó, tiene sentido que SQL Server elija el índice físico más pequeño con el que asociar la clave externa. Si cambia el script para crear la restricción única como
CLUSTERED
, el script "funciona" en 2008 R2. Pero ese comportamiento aún no está definido y no se debe confiar en él.Al igual que con la mayoría de las aplicaciones heredadas, solo tendrá que ir al grano y limpiar las cosas.
fuente
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:
Más detalles a continuación.
Considere la siguiente configuración:
donde tabla
TRef
pretende hacer referencia a la tablaT
.Para crear una restricción referencial, se puede usar el
ALTER TABLE
comando con dos alternativas:observe que en el segundo caso no se especifican columnas de la tabla a la que se hace referencia (
REFERENCES T
versusREFERENCES 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:
El segundo comando, sin embargo, devuelve un error diferente:
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
:el comando para la
FK_TRef_T_1
creación tiene éxito, pero el comando para laFK_TRef_T_2
creación todavía falla con Msg 1773.Finalmente, si agregamos clave primaria en
T
: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 tablaTRef
:esto devuelve:
ver que
FK_TRef_T_2
corresponde aPK_T
.Entonces, sí, con el uso de la
REFERENCES T
sintaxis, la clave externa deTRef
se asigna a la clave primaria deT
.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
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 cantidadindex_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.fuente