web-dev-qa-db-fra.com

AVEC VÉRIFICATION AJOUTER CONTRAINT suivi de VÉRIFICATION CONTRAINT vs. AJOUT CONTRAINTE

Je regarde l'exemple de base de données AdventureWorks pour SQL Server 2008 et je vois dans leurs scripts de création qu'ils ont tendance à utiliser les éléments suivants:

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

suivi immédiatement par:

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

Je le vois pour les clés étrangères (comme ici), les contraintes uniques et les contraintes régulières CHECK; Les contraintes DEFAULT utilisent le format habituel avec lequel je suis plus familier, tel que:

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

Quelle est la différence, le cas échéant, entre le faire de la première manière par rapport à la seconde?

119
Wayne Molina

La première syntaxe est redondante - WITH CHECK est la valeur par défaut pour les nouvelles contraintes, et la contrainte est également activée par défaut.

Cette syntaxe est générée par le studio de gestion SQL lors de la génération de scripts SQL - je suppose qu'il s'agit d'une sorte de redondance supplémentaire, éventuellement pour garantir que la contrainte est activée même si le comportement de contrainte par défaut pour une table est modifié.

87
Chris Hynes

Pour démontrer comment cela fonctionne--

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;
40
Graeme

Outre les excellents commentaires ci-dessus sur les contraintes de confiance:

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

Une contrainte non fiable, tout comme son nom l'indique, ne peut pas être approuvée pour représenter avec précision l'état des données dans la table pour le moment. Cependant, il est possible, mais fiable, de vérifier les données ajoutées et modifiées ultérieurement.

En outre, l'optimiseur de requête ne tient pas compte des contraintes non fiables.

Le code pour activer les contraintes de vérification et les contraintes de clé étrangère est assez mauvais, avec trois significations du mot "vérification".

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".
20
Greenstone Walker

WITH NOCHECK est également utilisé lorsque la table contient des données existantes qui ne sont pas conformes à la contrainte définie et que vous ne voulez pas que celle-ci s’exécute avec la nouvelle contrainte que vous implémentez ...

15
noonand

WITH CHECK est bien le comportement par défaut, mais il est recommandé d’inclure dans votre codage.

Le comportement alternatif est bien sûr d'utiliser WITH NOCHECK, il est donc bon de définir explicitement vos intentions. Ceci est souvent utilisé lorsque vous jouez avec/modifiez/changez de partition en ligne.

13
John Sansom

Les contraintes de clé étrangère et de vérification ont le concept d'être approuvées ou non, tout en étant activées et désactivées. Voir la page MSDN pour ALTER TABLE pour plus de détails.

WITH CHECK est la valeur par défaut pour l’ajout d’une nouvelle clé étrangère et les contraintes de vérification, WITH NOCHECK est la valeur par défaut pour la réactivation de la clé étrangère désactivée et des contraintes de vérification. Il est important d'être conscient de la différence.

Cela dit, toutes les déclarations apparemment redondantes générées par les services publics sont simplement là pour plus de sécurité et/ou pour faciliter le codage. Ne t'inquiète pas pour eux.

9
Christian Hayter

Voici un code que j'ai écrit pour nous aider à identifier et à corriger les CONTRAINTES non fiables dans une base de données. Il génère le code pour résoudre chaque problème.

    ;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;
7
Graeme