2012-02-07 12 views
11

Qualcuno può spiegare perché il terzo inserto (contrassegnato con dati di query) nel codice sottostante sia consentito da SQL Server?Perché il mio vincolo di controllo non blocca questo inserto nullo?

Per quanto posso dire, il vincolo di controllo dovrebbe consentire solo:

  • Code è nullo e System è nullo.
  • Code non è nullo e System è 1.

Il mio primo pensiero è stato ANSI NULLS, ma impostandoli on o off fatto alcuna differenza.

Questo è un esempio semplificato di un problema più grande che abbiamo riscontrato nella nostra applicazione (il sistema è stato confrontato con un elenco di numeri - IN(1, 2, etc.)). Abbiamo sostituito questo controllo con una chiave esterna (anziché IN) e un nuovo vincolo di controllo che consentiva l'uno o l'altro o entrambi non null; fare ciò ha impedito il terzo inserto.

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 

risposta

11

Il risultato della valutazione del vincolo corrente per i valori 123, NULL è indefinito.

  • ([Code] IS NULL AND [System] IS NULL) viene valutato come False
  • ([Code] IS NOT NULL AND [System] IN (1, 2, 3)) viene valutato come Undefined

risultato è Undefined

Check Constraint

vincoli CHECK rifiutano i valori che restituiscono FALSO. Poiché i valori nulli vengono valutati su UNKNOWN, la loro presenza nelle espressioni potrebbe ignorare lo vincolo.

si dovrebbe cambiare il vostro assegno per [System] IN (1, 2, 3) a ISNULL([System], 0) IN (1, 2, 3).

vostro vincolo di controllo diventa allora

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

Non metterei (False) tra parentesi dopo undefined. Non è assolutamente falso. –

+0

@Damien_The_Unbeliever - So cosa intendi ma l'ho aggiunto tra parentesi su cosa "non definito" ha verso il risultato finale. Aggiungerò questo commento alla risposta. –

+2

Ma quello che hai aggiunto non è vero. Se il risultato finale del vincolo di controllo è 'UNKNOWN', allora è trattato come se fosse valutato come' TRUE' - questo è ciò che ha sorpreso l'OP. –

13

benvenuti negli splendidi tre la logica del valore di SQL. Come potresti o non essere a conoscenza, il risultato di qualsiasi confronto standard a null non è TRUE o FALSE, ma UNKNOWN.

In una clausola WHERE, l'intera clausola deve essere valutata come TRUE.

In un vincolo CHECK, l'intero vincolo deve valutare come non FALSE.

Così, abbiamo:

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

che diventa (per i dati di query):

(FALSE AND TRUE) 
OR 
(TRUE AND UNKNOWN) 

E qualsiasi operatore con una UNKNOWN su un lato o l'altro valutata come UNKNOWN, in modo che il complesso il risultato è UNKNOWN. Quale non è FALSE e quindi la valutazione del vincolo di controllo ha esito positivo.


Se si desidera System di non essere nulla, è più chiaro a me se si aggiunge che, come un requisito esplicito supplementare.

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

Può sembrare un po 'strano il modo in cui questo è definito, ma è coerente con il modo in cui altri vincoli di lavoro - per esempio un vincolo di chiave esterna può avere colonne nullable e se una di queste colonne è nulla, non deve esserci una riga corrispondente nella tabella di riferimento.

+0

Grazie, ho avuto l'impressione che l'impostazione di ANSI_NULL fosse un modo per controllare questo comportamento? – DaveShaw

+0

@DaveShaw - Penso che sia necessario impostarlo 'OFF', che può avere altri effetti collaterali – JNK

+0

@JNK Ho provato sia acceso che spento e ho ottenuto gli stessi risultati. – DaveShaw

Problemi correlati