З ПЕРЕВІРИТИ ДОБАВИТИ ОГРАНИЧЕННЯ, а потім ПЕРЕВІРТИ КОНСТРАКТ проти ДОДАТИ КОНСТРАНТ


133

Я переглядаю зразок бази даних 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

Яка різниця, якщо така є, між тим, як зробити це першим способом проти другого?

Відповіді:


94

Перший синтаксис є надлишковим - ЗНАЙДАЄТЬСЯ З ПОВЕРНЕННЯМИ ЗА новими обмеженнями, а обмеження також увімкнено за замовчуванням.

Цей синтаксис генерується студією управління SQL під час генерації сценаріїв sql - я припускаю, що це якась додаткова надмірність, можливо, щоб забезпечити включення обмеження, навіть якщо поведінка обмежень за замовчуванням для таблиці буде змінена.


12
Це не схоже, що З ПЕРЕВІРИТЕ насправді це типово, це лише типово для нових даних. Із msdn.microsoft.com/en-us/library/ms190273.aspx : "Якщо не вказано, ЗНАЙТИ ПРОВЕРКАТИ на нові обмеження, а З 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
Спочатку мені це було незрозуміло читати. Другий (надлишковий) рядок - це функція включення обмеження. Оскільки обмеження включено за замовчуванням, другий рядок є зайвим.
сліпий

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;

7
Прибираємо-- DROP TABLE T2; DROP TABLE T1;
Graeme

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 використовується також тоді, коли в таблиці є дані, які не відповідають обмеженню, як визначено, і ви не хочете, щоб він стикався з новим обмеженням, яке ви реалізуєте ...


13

WITH CHECK насправді поведінка за замовчуванням, однак, це добра практика, щоб включити до кодування.

Альтернативну поведінку, звичайно, використовувати WITH NOCHECK, тому добре чітко визначити свої наміри. Це часто використовується, коли ви граєте з / зміною / перемиканням вбудованих розділів.


9

Обмеження на зовнішній ключ та чек мають поняття довіри або недовіри, а також їх увімкнення та вимкнення. Для отримання ALTER TABLEдетальної інформації див. Сторінку MSDN .

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

Сказавши це, будь-які, очевидно, зайві заяви, що створюються утилітами, просто існують для безпеки та / або простоти кодування. Не хвилюйся за них.


Це посилання, на яке ви посилаєтесь : msdn.microsoft.com/en-us/library/ms190273.aspx ? Чи означає це, що ми повинні робити таблицю ALTER TABLE AND CHECK CHECK CONSTRAINT ALL, а не робити це для кожного обмеження?
Генрік Стаун Поульсен

@HenrikStaunPoulsen: Так, це посилання. Ніщо не перешкоджає включенню кожного обмеження окремо, але ви повинні сказати, WITH CHECK CHECK CONSTRAINTщоб довіряти їм.
Крістіан Хейтер

Я спробував це; Я побіг "ALTER TABLE [dfm]. [TRATransformError] С 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)знайти відключені та ненадійні обмеження. Після видачі відповідних заяв змінної таблиці, як зазначено вище, ці обмеження зникають із запиту, тому я можу бачити, як він працює.
Крістіан Хейтер

2
@HenrikStaunPoulsen, це тому, що прапор not_for_replication встановлений на 1. Це робить обмеження як недовірене. SELECT ім'я, 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 Майте на увазі, що видалення та оновлення не вказані в цьому сценарії, і вам потрібно це врахувати.
kuklei

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.