2012-02-07 10 views
11

Czy ktoś może wyjaśnić, dlaczego trzecia wkładka (oznaczona Query Data) w poniższym kodzie jest dozwolona przez SQL Server?Dlaczego moje ograniczenie sprawdzające nie zatrzymuje tej pustej wkładki?

O ile mogę powiedzieć, ograniczenie wyboru powinno pozwolić tylko:

  • Code jest nieważna i System jest null.
  • Code nie jest pusty i System jest 1.

Moja pierwsza myśl była ANSI NULLS, ale ich ustawienie on lub off żadnej różnicy.

Jest to uproszczony przykład większego problemu, który znaleźliśmy w naszej aplikacji (System sprawdzono pod kątem listy numerów - IN(1, 2, etc.)). Zastąpiliśmy to sprawdzenie kluczem obcym (zamiast IN) i nowym ograniczeniem sprawdzającym, które pozwoliło na użycie zarówno wartości zerowej, jak i zerowej; to uniemożliwiło trzecią wstawkę.

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 

Odpowiedz

11

Wynikiem oceny aktualnego ograniczenia dla wartości 123, NULL jest niezdefiniowany.

  • ([Code] IS NULL AND [System] IS NULL) ocenia się False
  • ([Code] IS NOT NULL AND [System] IN (1, 2, 3)) ocenia się Undefined

Wynik jest Undefined

Check Constraint

ograniczenia CHECK odrzucić wartości, które do oceny FAŁSZYWE. Ponieważ wartości NULL są oceniane jako NIEZNANE, ich obecność w wyrażeniach może przesłonić wiązanie .

Należy zmienić czek na [System] IN (1, 2, 3) na ISNULL([System], 0) IN (1, 2, 3).

Twój Ograniczenie sprawdzające staje

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 ???? 
) 
+2

Nie wstawiłbym (False) w nawiasach po undefined. Z pewnością nie jest to fałsz. –

+0

@Damien_The_Unbeliever - Wiem co masz na myśli, ale dodałem w nawiasie, co 'niezdefiniowane' robi w kierunku wyniku końcowego. Dodam ten komentarz do odpowiedzi. –

+2

Ale to, co dodałeś, nie jest prawdą. Jeśli ostatecznym wynikiem ograniczenia sprawdzającego jest "NIEZNANE", to jest traktowane tak samo, jakby było oceniane jako "PRAWDA" - to właśnie zaskoczyło PO. –

13

Zapraszamy do wspaniałego trzy ceniony logiki SQL. Jak możesz lub nie być świadomy, wynik dowolnego standardowego porównania z null nie jest TRUE ani , ale UNKNOWN.

W klauzuli WHERE cała klauzula musi zostać oceniona jako TRUE.

W więzieniu CHECK całe ograniczenie musi zostać ocenione jako nie FALSE.

Więc mamy:

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

który staje się (dla danych zapytań):

(FALSE AND TRUE) 
OR 
(TRUE AND UNKNOWN) 

a wszelkie operatora posiadającego UNKNOWN po jednej lub drugiej stronie daje wartość UNKNOWN, więc ogólna wynikiem jest UNKNOWN. Który nie jest FALSE, a więc ocena ograniczenia kontroli zakończyła się powodzeniem.


Jeśli chcesz System nie być null, to oczywistym dla mnie jeśli dodać, że jako dodatkowy wyraźnego wymogu.

([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 ???? 

To może wydawać się odrobinę dziwne, że ten sposób jest zdefiniowany, ale jest ona zgodna z okazji, że inne ograniczenia pracy - na przykład Ograniczenie klucza obcego może mieć kolumny z wartościami zerowymi, a jeśli którakolwiek z tych kolumn ma wartość NULL, nie musi występować pasujący wiersz w przywoływanej tabeli.

+0

Dzięki, miałem wrażenie, że ustawienie ANSI_NULL było sposobem kontrolowania tego zachowania? – DaveShaw

+0

@DaveShaw - Myślę, że musisz ustawić to 'OFF', które może mieć inne efekty uboczne – JNK

+0

@JNK Próbowałem zarówno włączać, jak i wyłączać, i otrzymałem takie same wyniki. – DaveShaw

Powiązane problemy