WITH CHECK ADD CONSTRAINT seguido por CHECK CONSTRAINT vs. ADD CONSTRAINT

133

Estou analisando o banco de dados de exemplo AdventureWorks para SQL Server 2008 e vejo nos scripts de criação que eles tendem a usar o seguinte:

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

seguido imediatamente por:

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

Eu vejo isso para chaves estrangeiras (como aqui), restrições exclusivas e CHECKrestrições regulares ; DEFAULTrestrições usam o formato regular com o qual estou mais familiarizado, como:

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

Qual é a diferença, se houver, entre fazê-lo da primeira maneira versus a segunda?

Wayne Molina
fonte

Respostas:

94

A primeira sintaxe é redundante - o WITH CHECK é o padrão para novas restrições e a restrição também é ativada por padrão.

Essa sintaxe é gerada pelo SQL Management Studio ao gerar scripts sql - suponho que seja algum tipo de redundância extra, possivelmente para garantir que a restrição seja ativada mesmo que o comportamento da restrição padrão para uma tabela seja alterado.

Chris Hynes
fonte
12
Não parece que WITH CHECK é realmente o padrão, é apenas o padrão para novos dados. De msdn.microsoft.com/en-us/library/ms190273.aspx : "Se não especificado, WITH CHECK será assumido para novas restrições e WITH NOCHECK será assumido para restrições reativadas."
Zain Rizvi
8
@ZainRizvi: não novos dados, novas restrições. Se você desabilitar uma restrição ALTER TABLE foo NOCHECK CONSTRAINT fk_be depois reativá-la com ALTER TABLE foo CHECK CONSTRAINT fk_bela, não verificará a restrição. ALTER TABLE foo WITH CHECK CHECK CONSTRAINT fk_bé necessário para que os dados sejam verificados.
jmoreno
2
Não estava claro para mim lendo isso inicialmente. A segunda linha (redundante) é a função de ativar a restrição. Como a restrição está ativada por padrão, a segunda linha é redundante.
Blindguy
47

Para demonstrar como isso funciona--

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
fonte
7
Limpe up-- DROP TABLE T2; DROP TABLE T1;
Graeme
8
Eu adicionei o código de limpeza do seu comentário à sua resposta real para ajudar as pessoas a copiar e passar a noite lá fora.
Mkolfe02
18
"Copia-e-passa-a-noite" parece um pouco negativo. Eu me consideraria um desses usuários da pilha (para termos mais positivos ...) "que consideram esses tipos de exemplos detalhados extremamente valiosos".
GaTechThomas 13/01
2
Derrogatório ou não, 'voar à noite' parece que me descreve perfeitamente.
sanepete
21

Além dos excelentes comentários acima sobre restrições confiáveis:

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

Uma restrição não confiável, como o nome sugere, não pode ser confiável para representar com precisão o estado dos dados na tabela no momento. No entanto, pode ser confiável para verificar os dados adicionados e modificados no futuro.

Além disso, restrições não confiáveis ​​são desconsideradas pelo otimizador de consultas.

O código para ativar restrições de verificação e restrições de chave estrangeira é bastante ruim, com três significados da palavra "verificação".

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".
Andarilho do Greenstone
fonte
15

WITH NOCHECK também é usado quando se tem dados existentes em uma tabela que não está em conformidade com a restrição definida e você não deseja que ela colide com a nova restrição que você está implementando ...

meio-dia
fonte
13

WITH CHECK é realmente o comportamento padrão, no entanto, é uma boa prática incluir na sua codificação.

É claro que o comportamento alternativo é usado WITH NOCHECK, por isso é bom definir explicitamente suas intenções. Isso geralmente é usado quando você está tocando com / modificando / alternando partições embutidas.

John Sansom
fonte
9

As restrições de chave estrangeira e verificação têm o conceito de serem confiáveis ​​ou não confiáveis, além de serem ativadas e desativadas. Consulte a página do MSDN ALTER TABLEpara obter detalhes completos.

WITH CHECKé o padrão para adicionar novas chaves estrangeiras e verificar restrições, WITH NOCHECKé o padrão para reativar chaves estrangeiras desativadas e verificar restrições. É importante estar ciente da diferença.

Dito isto, quaisquer declarações aparentemente redundantes geradas pelos utilitários estão simplesmente lá para segurança e / ou facilidade de codificação. Não se preocupe com eles.

Christian Hayter
fonte
É este link que você está se referindo: msdn.microsoft.com/en-us/library/ms190273.aspx ? Isso significa que precisamos fazer uma tabela ALTER TABLE COM CHECK CHECK CONSTRAINT ALL em vez de fazê-lo para cada restrição?
Henrik Staun Poulsen
@HenrikStaunPoulsen: Sim, esse é o link. Não há nada que o impeça de ativar cada restrição individualmente, mas é preciso dizer WITH CHECK CHECK CONSTRAINTpara que eles sejam confiáveis.
Christian Hayter
Eu tentei isso; Executei "ALTER TABLE [dfm]. [TRATransformError] COM CHECK CHECK CONSTRAINT [FK_TRATransformError_ETLEvent]". Mas o FK ainda tem Is_Not_Trusted = 1. Em seguida, larguei o FK e o recriei com "WITH CHECK CHECK" e agora tenho Is_Not_Trusted = 0. Finalmente. Você sabe por quê? Por favor note que eu sempre tive is_not_for_replication = 0
Henrik Staun Poulsen
@HenrikStaunPoulsen: Eu não sei, sempre funcionou bem para mim. Eu executo uma consulta como select * from sys.objects where [type] in ('C', 'F') and (objectproperty([object_id], 'CnstIsDisabled') = 1 or objectproperty([object_id], 'CnstIsNotTrusted') = 1)encontrar restrições desabilitadas e não confiáveis. Depois de emitir as instruções apropriadas da tabela alter, como acima, essas restrições desaparecem da consulta, para que eu possa vê-la funcionando.
Christian Hayter
2
@HenrikStaunPoulsen, é porque o sinalizador not_for_replication está definido como 1. Isso faz com que a restrição não seja confiável. SELECT name, create_date, modify_date, is_disabled, is_not_for_replication, is_not_trusted FROM sys.foreign_keys WHERE is_not_trusted = 1 Nesse caso, você deve eliminar e recriar a restrição. Eu uso isso para concluir que gist.github.com/smoothdeveloper/ea48e43aead426248c0f Lembre-se de que ao excluir e atualizar não são especificados neste script e você deve levar isso em consideração.
Kuklei 14/09/19
8

Aqui está um código que escrevi para nos ajudar a identificar e corrigir CONSTRAINTs não confiáveis ​​em um banco de dados. Ele gera o código para corrigir 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
fonte