Почему мое проверочное ограничение не останавливает эту пустую вставку? - PullRequest
13 голосов
/ 07 февраля 2012

Может кто-нибудь объяснить, почему третья вставка (помеченная Данные запроса ) в приведенном ниже коде разрешена SQL Server?

Насколько я могу судить, ограничение проверки должно позволять только:

  • Code равно нулю и System равно нулю.
  • Code не равно нулю, а System равно 1.

Моей первой мыслью было ANSI NULLS, но установка их on или off не изменила.

Это упрощенный пример более крупной проблемы, которую мы обнаружили в нашем приложении (система проверена на соответствиесписок номеров - IN(1, 2, etc.)).Мы заменили эту проверку внешним ключом (вместо IN) и новым проверочным ограничением, которое допускает либо null, либо оба not null;это предотвратило третью вставку.

IF  EXISTS (SELECT * FROM sys.check_constraints WHERE object_id = OBJECT_ID(N'[dbo].[CK_TestCheck]') AND parent_object_id = OBJECT_ID(N'[dbo].[TestCheck]'))
    ALTER TABLE [dbo].[TestCheck] DROP CONSTRAINT [CK_TestCheck]
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestCheck]') AND type in (N'U'))
    DROP TABLE [dbo].[TestCheck]
GO

SET ANSI_NULLS ON
GO

CREATE TABLE TestCheck(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Code] [varchar](50) NULL,
    [System] [tinyint] NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC))
GO

ALTER TABLE [dbo].[TestCheck] WITH CHECK ADD CONSTRAINT [CK_TestCheck] CHECK
(
    ([Code] IS NULL AND [System] IS NULL)   --Both null
    OR
    ([Code] IS NOT NULL AND [System] = 1)   --Both not null ????
)
GO

ALTER TABLE [dbo].[TestCheck] CHECK CONSTRAINT [CK_TestCheck]
GO

--Good Data
insert TestCheck (Code, [System]) Values(null, null);
insert TestCheck (Code, [System]) Values('123', 1);

--Query Data
insert TestCheck (Code, [System]) Values('123', null);

--Bad data stopped
insert TestCheck (Code, [System]) Values(null, 1);
insert TestCheck (Code, [System]) Values('123', 4);

select * from TestCheck
Where
    case when
    (
        ([Code] IS NULL AND [System] IS NULL)           --Both null
        OR
        ([Code] IS NOT NULL AND [System] in (1, 2, 3))  --Both not null ????
    )
    then 0 else 1 end
     = 1

Ответы [ 2 ]

16 голосов
/ 07 февраля 2012

Добро пожаловать в замечательную трехзначную логику SQL.Как вы можете знать или не знать, результатом любого стандартного сравнения с null будет не TRUE или FALSE, а UNKNOWN.

В предложении WHERE всепредложение должно оцениваться как TRUE.

В ограничении CHECK все ограничение должно оцениваться как не FALSE.

Итак, мы имеем:

([Code] IS NULL AND [System] IS NULL)   --Both null
OR
([Code] IS NOT NULL AND [System] = 1)   --Both not null ????

Что становится (для данных запроса):

(FALSE AND TRUE)
OR
(TRUE AND UNKNOWN)

И любой оператор с UNKNOWN на одной или другой стороне оценивается как UNKNOWN, поэтому общий результат равен UNKNOWN.Который не FALSE, и поэтому проверка ограничения проверки прошла успешно.


Если вы хотите, чтобы System не был нулевым, мне будет понятно, если вы добавите это как дополнительное явное требование.

([Code] IS NULL AND [System] IS NULL)   --Both null
OR
([Code] IS NOT NULL AND [System] IS NOT NULL AND [System] = 1)   --Both not null ????

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

12 голосов
/ 07 февраля 2012

Результат оценки текущего ограничения для значений 123, NULL не определен.

  • ([Code] IS NULL AND [System] IS NULL) оценивается как False
  • ([Code] IS NOT NULL AND [System] IN (1, 2, 3)) оценивается как Undefined

Результат - Undefined

Проверка ограничения

Ограничения CHECK отклоняют значения, которые оцениваются как FALSE.Поскольку нулевые значения имеют значение UNKNOWN, их присутствие в выражениях может переопределить ограничение.

Вы должны изменить свой чек на [System] IN (1, 2, 3) на ISNULL([System], 0) IN (1, 2, 3).

Ваш чекограничение становится

ALTER TABLE [dbo].[TestCheck] WITH CHECK ADD CONSTRAINT [CK_TestCheck] CHECK
(
    ([Code] IS NULL AND [System] IS NULL)   --Both null
    OR
    ([Code] IS NOT NULL AND ISNULL([System], 0) IN (1, 2, 3))   --Both not null ????
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...