С ПРОВЕРОМ ДОБАВИТЬ ОГРАНИЧЕНИЕ, за которым следует ПРОВЕРИТЬ ОГРАНИЧЕНИЕ против ДОБАВИТЬ ОГРАНИЧЕНИЕ

134

Я смотрю образец базы данных AdventureWorks для SQL Server 2008 и вижу в их сценариях создания, что они, как правило, используют следующее:

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

сразу после этого:

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

Я вижу это для внешних ключей (как здесь), уникальных ограничений и обычных CHECKограничений; DEFAULTограничения используют обычный формат, с которым я более знаком, например:

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

В чем разница между первым и вторым способами?

Уэйн Молина
источник

Ответы:

95

Первый синтаксис является избыточным - WITH CHECK используется по умолчанию для новых ограничений, и ограничение также включено по умолчанию.

Этот синтаксис генерируется студией управления SQL при создании сценариев sql - я предполагаю, что это какая-то дополнительная избыточность, возможно, для обеспечения включения ограничения, даже если поведение ограничения по умолчанию для таблицы изменилось.

Крис Хайнс
источник
12
Не похоже, что WITH CHECK используется по умолчанию, это только значение по умолчанию для новых данных. Из msdn.microsoft.com/en-us/library/ms190273.aspx : «Если не указано, WITH CHECK предполагается для новых ограничений, а WITH NOCHECK - для повторно включенных ограничений».
Зайн Ризви
8
@ZainRizvi: не новые данные, а новые ограничения. Если вы отключите ограничение с помощью, ALTER TABLE foo NOCHECK CONSTRAINT fk_bа затем снова включите его, ALTER TABLE foo CHECK CONSTRAINT fk_bэто не проверяет ограничение. ALTER TABLE foo WITH CHECK CHECK CONSTRAINT fk_bнеобходимо для проверки данных.
jmoreno
2
Сначала мне было непонятно, как это читать. Вторая (избыточная) строка - это функция для включения ограничения. Поскольку ограничение включено по умолчанию, вторая строка является избыточной.
blindguy
47

Чтобы продемонстрировать, как это работает -

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
источник
7
Уборка DROP TABLE T2; DROP TABLE T1;
Грэм
8
Я добавил код очистки из вашего комментария к вашему фактическому ответу, чтобы помочь тамошним копировщикам-однодневкам.
mwolfe02
18
"Ночные переписчики" кажутся немного негативными. Я бы считал себя одним из тех пользователей стека (для более позитивной формулировки ...), «которые считают такие подробные примеры чрезвычайно ценными».
GaTechThomas
2
Унизительно или нет, но «летать ночью» кажется, что оно идеально меня описывает.
sanepete
21

В дополнение к вышеприведенным отличным комментариям о надежных ограничениях:

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

Недоверяемому ограничению, как следует из его названия, нельзя доверять, чтобы точно представлять состояние данных в таблице прямо сейчас. Однако можно, но можно доверять проверке данных, добавляемых и изменяемых в будущем.

Кроме того, оптимизатор запросов игнорирует ненадежные ограничения.

Код для включения ограничений проверки и ограничений внешнего ключа довольно плох, с тремя значениями слова «проверка».

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".
Гринстоун Уолкер
источник
15

WITH NOCHECK также используется, когда в таблице есть существующие данные, которые не соответствуют определенному ограничению, и вы не хотите, чтобы оно противоречило новому ограничению, которое вы реализуете ...

noonand
источник
13

WITH CHECK действительно является поведением по умолчанию, однако рекомендуется включать его в код.

Разумеется, следует использовать альтернативное поведение WITH NOCHECK, поэтому желательно четко определить свои намерения. Это часто используется, когда вы играете / изменяете / переключаете встроенные разделы.

Джон Сансом
источник
9

Ограничения внешнего ключа и проверки имеют понятие «доверенные» или «ненадежные», а также могут быть включены и отключены. См. Страницу MSDN для ALTER TABLEполучения полной информации.

WITH CHECK используется по умолчанию для добавления нового внешнего ключа и проверки ограничений, WITH NOCHECK является значением по умолчанию для повторного включения отключенного внешнего ключа и проверочных ограничений. Важно понимать разницу.

Сказав это, любые явно избыточные операторы, генерируемые утилитами, просто существуют для безопасности и / или простоты кодирования. Не беспокойся о них.

Кристиан Хейтер
источник
Это ссылка, на которую вы ссылаетесь : msdn.microsoft.com/en-us/library/ms190273.aspx ? Означает ли это, что мы должны выполнять ALTER TABLE table WITH CHECK CHECK CONSTRAINT ALL вместо того, чтобы делать это для каждого ограничения?
Хенрик Стаун Поульсен,
@HenrikStaunPoulsen: Да, это ссылка. Ничто не мешает вам включить каждое ограничение по отдельности, но вы должны сказать, WITH CHECK CHECK CONSTRAINTчтобы им доверяли.
Кристиан Хейтер,
Я пробовал это; Я выполнил "ALTER TABLE [dfm]. [TRATransformError] WITH CHECK CHECK CONSTRAINT [FK_TRATransformError_ETLEvent]". Но у FK по-прежнему Is_Not_Trusted = 1. Затем я отбросил FK и воссоздал его с помощью «WITH CHECK CHECK», и теперь у меня Is_Not_Trusted = 0. Наконец. Ты знаешь почему? Обратите внимание, что у меня всегда было is_not_for_replication = 0
Хенрик Стаун Поулсен,
@HenrikStaunPoulsen: Не знаю, у меня это всегда хорошо работало. Я запускаю запрос, например, select * from sys.objects where [type] in ('C', 'F') and (objectproperty([object_id], 'CnstIsDisabled') = 1 or objectproperty([object_id], 'CnstIsNotTrusted') = 1)чтобы найти отключенные и ненадежные ограничения. После выполнения соответствующих операторов alter table, как указано выше, эти ограничения исчезают из запроса, поэтому я могу видеть, как он работает.
Кристиан Хейтер,
2
@HenrikStaunPoulsen, это потому, что флаг not_for_replication установлен в 1. Это делает ограничение ненадежным. SELECT name, create_date, modify_date, is_disabled, is_not_for_replication, is_not_trusted FROM sys.foreign_keys WHERE is_not_trusted = 1 В этом случае вам нужно отбросить и воссоздать ограничение. Я использую это для достижения цели gist.github.com/smoothdeveloper/ea48e43aead426248c0f Имейте в виду, что удаление и обновление не указаны в этом скрипте, и вы должны это учитывать.
куклей
8

Вот код, который я написал, чтобы помочь нам определить и исправить ненадежные ОГРАНИЧЕНИЯ в БАЗЕ ДАННЫХ. Он генерирует код для исправления каждой проблемы.

    ;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
источник