CON CHECK ADD CONSTRAINT seguido de CHECK CONSTRAINT vs. ADD CONSTRAINT

133

Estoy mirando la base de datos de ejemplo AdventureWorks para SQL Server 2008, y veo en sus scripts de creación que tienden a usar lo siguiente:

ALTER TABLE [Production].[ProductCostHistory] WITH CHECK ADD 
CONSTRAINT [FK_ProductCostHistory_Product_ProductID] FOREIGN KEY([ProductID])
  REFERENCES [Production].[Product] ([ProductID])
GO

seguido inmediatamente por:

ALTER TABLE [Production].[ProductCostHistory] CHECK CONSTRAINT     
[FK_ProductCostHistory_Product_ProductID]
GO

Veo esto para claves foráneas (como aquí), restricciones únicas y CHECKrestricciones regulares ; DEFAULTlas restricciones usan el formato normal con el que estoy más familiarizado, como por ejemplo:

ALTER TABLE [Production].[ProductCostHistory] ADD  CONSTRAINT  
[DF_ProductCostHistory_ModifiedDate]  DEFAULT (getdate()) FOR [ModifiedDate]
GO

¿Cuál es la diferencia, si hay alguna, entre hacerlo de la primera manera versus la segunda?

Wayne Molina
fuente

Respuestas:

94

La primera sintaxis es redundante: WITH CHECK es el valor predeterminado para nuevas restricciones, y la restricción también se activa de forma predeterminada.

Esta sintaxis es generada por el estudio de administración de SQL cuando genera scripts sql: supongo que es una especie de redundancia adicional, posiblemente para garantizar que la restricción esté habilitada incluso si se cambia el comportamiento de restricción predeterminado para una tabla.

Chris Hynes
fuente
12
No parece que WITH CHECK sea el valor predeterminado, solo es el valor predeterminado para los datos nuevos. De msdn.microsoft.com/en-us/library/ms190273.aspx : "Si no se especifica, se asume WITH CHECK para nuevas restricciones, y WITH NOCHECK para restricciones re-habilitadas".
Zain Rizvi
8
@ZainRizvi: no nuevos datos, nuevas restricciones. Si deshabilita una restricción con ALTER TABLE foo NOCHECK CONSTRAINT fk_by luego la vuelve a habilitar, ALTER TABLE foo CHECK CONSTRAINT fk_bno verificará la restricción. ALTER TABLE foo WITH CHECK CHECK CONSTRAINT fk_bes necesario para tener los datos verificados.
jmoreno
2
No estaba claro para mí leer esto inicialmente. La segunda línea (redundante) es la función para activar la restricción. Como la restricción está activada por defecto, la segunda línea es redundante.
ciego
47

Para demostrar cómo funciona esto ...

CREATE TABLE T1 (ID INT NOT NULL, SomeVal CHAR(1));
ALTER TABLE T1 ADD CONSTRAINT [PK_ID] PRIMARY KEY CLUSTERED (ID);

CREATE TABLE T2 (FKID INT, SomeOtherVal CHAR(2));

INSERT T1 (ID, SomeVal) SELECT 1, 'A';
INSERT T1 (ID, SomeVal) SELECT 2, 'B';

INSERT T2 (FKID, SomeOtherVal) SELECT 1, 'A1';
INSERT T2 (FKID, SomeOtherVal) SELECT 1, 'A2';
INSERT T2 (FKID, SomeOtherVal) SELECT 2, 'B1';
INSERT T2 (FKID, SomeOtherVal) SELECT 2, 'B2';
INSERT T2 (FKID, SomeOtherVal) SELECT 3, 'C1';  --orphan
INSERT T2 (FKID, SomeOtherVal) SELECT 3, 'C2';  --orphan

--Add the FK CONSTRAINT will fail because of existing orphaned records
ALTER TABLE T2 ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);   --fails

--Same as ADD above, but explicitly states the intent to CHECK the FK values before creating the CONSTRAINT
ALTER TABLE T2 WITH CHECK ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);    --fails

--Add the CONSTRAINT without checking existing values
ALTER TABLE T2 WITH NOCHECK ADD CONSTRAINT FK_T2_T1 FOREIGN KEY (FKID) REFERENCES T1 (ID);  --succeeds
ALTER TABLE T2 CHECK CONSTRAINT FK_T2_T1;   --succeeds since the CONSTRAINT is attributed as NOCHECK

--Attempt to enable CONSTRAINT fails due to orphans
ALTER TABLE T2 WITH CHECK CHECK CONSTRAINT FK_T2_T1;    --fails

--Remove orphans
DELETE FROM T2 WHERE FKID NOT IN (SELECT ID FROM T1);

--Enabling the CONSTRAINT succeeds
ALTER TABLE T2 WITH CHECK CHECK CONSTRAINT FK_T2_T1;    --succeeds; orphans removed

--Clean up
DROP TABLE T2;
DROP TABLE T1;
Graeme
fuente
77
Limpiar-- DROP TABLE T2; DROP TABLE T1;
Graeme
8
Agregué el código de limpieza de su comentario a su respuesta real para ayudar a copiar y pegar los volantes de noche.
mwolfe02
18
"Fly-by-night copy-and-pasters" parece un poco negativo. Me consideraría uno de esos usuarios de la pila (para una redacción más positiva ...) "que encuentran este tipo de ejemplos detallados extremadamente valiosos".
GaTechThomas
2
Derogatorio o no, 'volar de noche' parece que me describe perfectamente.
sanepete
21

Además de los excelentes comentarios anteriores sobre restricciones confiables:

select * from sys.foreign_keys where is_not_trusted = 1 ;
select * from sys.check_constraints where is_not_trusted = 1 ;

Una restricción no confiable, como su nombre lo indica, no se puede confiar para representar con precisión el estado de los datos en la tabla en este momento. Sin embargo, se puede confiar para verificar los datos agregados y modificados en el futuro.

Además, el optimizador de consultas no tiene en cuenta las restricciones no confiables.

El código para habilitar restricciones de verificación y restricciones de clave externa es bastante malo, con tres significados de la palabra "verificación".

ALTER TABLE [Production].[ProductCostHistory] 
WITH CHECK -- This means "Check the existing data in the table".
CHECK CONSTRAINT -- This means "enable the check or foreign key constraint".
[FK_ProductCostHistory_Product_ProductID] -- The name of the check or foreign key constraint, or "ALL".
Caminante de piedra verde
fuente
15

WITH NOCHECK se usa también cuando uno tiene datos existentes en una tabla que no se ajusta a la restricción como se definió y no desea que esté en conflicto con la nueva restricción que está implementando ...

mediodía
fuente
13

WITH CHECK es el comportamiento predeterminado, sin embargo, es una buena práctica incluirlo dentro de su codificación.

El comportamiento alternativo es, por supuesto WITH NOCHECK, utilizar , por lo que es bueno definir explícitamente sus intenciones. Esto se usa a menudo cuando estás jugando con / modificando / cambiando particiones en línea.

John Sansom
fuente
9

Las restricciones de clave externa y verificación tienen el concepto de ser confiables o no confiables, así como de habilitarse y deshabilitarse. Vea la página de MSDN ALTER TABLEpara más detalles.

WITH CHECKes el valor predeterminado para agregar nuevas claves externas y verificar restricciones, WITH NOCHECKes el valor predeterminado para volver a habilitar la clave externa deshabilitada y verificar restricciones. Es importante ser consciente de la diferencia.

Dicho esto, cualquier declaración aparentemente redundante generada por las empresas de servicios públicos simplemente existe para la seguridad y / o facilidad de codificación. No te preocupes por ellos.

Christian Hayter
fuente
¿Es este enlace al que se refiere: msdn.microsoft.com/en-us/library/ms190273.aspx ? ¿Significa esto que tenemos que hacer una tabla ALTER TABLE WITH CHECK CHECK CONSTRAINT ALL en lugar de hacerlo para cada restricción?
Henrik Staun Poulsen
@HenrikStaunPoulsen: Sí, ese es el enlace. No hay nada que lo detenga al habilitar cada restricción individualmente, pero sí tiene que decir WITH CHECK CHECK CONSTRAINTpara que confíen en ellas.
Christian Hayter
Lo intenté; Ejecuté "ALTER TABLE [dfm]. [TRATransformError] CON CHECK CHECK CONSTRAINT [FK_TRATransformError_ETLEvent]". Pero el FK todavía tiene Is_Not_Trusted = 1. Luego dejé caer el FK y lo volví a crear con "WITH CHECK CHECK", y ahora tengo Is_Not_Trusted = 0. Al final. ¿Sabes por qué? Tenga en cuenta que siempre he tenido is_not_for_replication = 0
Henrik Staun Poulsen
@HenrikStaunPoulsen: No sé, siempre me ha funcionado bien. Ejecuto una consulta select * from sys.objects where [type] in ('C', 'F') and (objectproperty([object_id], 'CnstIsDisabled') = 1 or objectproperty([object_id], 'CnstIsNotTrusted') = 1)para encontrar restricciones deshabilitadas y no confiables. Después de emitir las declaraciones de alterar la tabla apropiadas como se indicó anteriormente, esas restricciones desaparecen de la consulta, por lo que puedo ver que funciona.
Christian Hayter
2
@HenrikStaunPoulsen, se debe a que el indicador not_for_replication está establecido en 1. Esto hace que la restricción no sea confiable. SELECCIONE nombre, create_date, modify_date, is_disabled, is_not_for_replication, is_not_trusted FROM sys.foreign_keys WHERE is_not_trusted = 1 En ese caso, debe soltar y volver a crear la restricción. Lo uso para lograr que gist.github.com/smoothdeveloper/ea48e43aead426248c0f Tenga en cuenta que al eliminar y actualizar no se especifican en este script y debe tenerlo en cuenta.
kuklei
8

Aquí hay un código que escribí para ayudarnos a identificar y corregir RESTRICCIONES no confiables en una BASE DE DATOS. Genera el código para solucionar cada problema.

    ;WITH Untrusted (ConstraintType, ConstraintName, ConstraintTable, ParentTable, IsDisabled, IsNotForReplication, IsNotTrusted, RowIndex) AS
(
    SELECT 
        'Untrusted FOREIGN KEY' AS FKType
        , fk.name AS FKName
        , OBJECT_NAME( fk.parent_object_id) AS FKTableName
        , OBJECT_NAME( fk.referenced_object_id) AS PKTableName 
        , fk.is_disabled
        , fk.is_not_for_replication
        , fk.is_not_trusted
        , ROW_NUMBER() OVER (ORDER BY OBJECT_NAME( fk.parent_object_id), OBJECT_NAME( fk.referenced_object_id), fk.name) AS RowIndex
    FROM 
        sys.foreign_keys fk 
    WHERE 
        is_ms_shipped = 0 
        AND fk.is_not_trusted = 1       

    UNION ALL

    SELECT 
        'Untrusted CHECK' AS KType
        , cc.name AS CKName
        , OBJECT_NAME( cc.parent_object_id) AS CKTableName
        , NULL AS ParentTable
        , cc.is_disabled
        , cc.is_not_for_replication
        , cc.is_not_trusted
        , ROW_NUMBER() OVER (ORDER BY OBJECT_NAME( cc.parent_object_id), cc.name) AS RowIndex
    FROM 
        sys.check_constraints cc 
    WHERE 
        cc.is_ms_shipped = 0
        AND cc.is_not_trusted = 1

)
SELECT 
    u.ConstraintType
    , u.ConstraintName
    , u.ConstraintTable
    , u.ParentTable
    , u.IsDisabled
    , u.IsNotForReplication
    , u.IsNotTrusted
    , u.RowIndex
    , 'RAISERROR( ''Now CHECKing {%i of %i)--> %s ON TABLE %s'', 0, 1' 
        + ', ' + CAST( u.RowIndex AS VARCHAR(64))
        + ', ' + CAST( x.CommandCount AS VARCHAR(64))
        + ', ' + '''' + QUOTENAME( u.ConstraintName) + '''' 
        + ', ' + '''' + QUOTENAME( u.ConstraintTable) + '''' 
        + ') WITH NOWAIT;'
    + 'ALTER TABLE ' + QUOTENAME( u.ConstraintTable) + ' WITH CHECK CHECK CONSTRAINT ' + QUOTENAME( u.ConstraintName) + ';' AS FIX_SQL
FROM Untrusted u
CROSS APPLY (SELECT COUNT(*) AS CommandCount FROM Untrusted WHERE ConstraintType = u.ConstraintType) x
ORDER BY ConstraintType, ConstraintTable, ParentTable;
Graeme
fuente