2012-02-07 6 views
11

¿Alguien puede explicar por qué el SQL Server permite el tercer inserto (etiquetado Query Data) en el siguiente código?¿Por qué mi restricción de verificación no detiene esta inserción nula?

Por lo que yo puedo decir, la restricción de control sólo debe permitir:

  • Code es nula y System es nulo.
  • Code no es nulo y System es 1.

Mi primer pensamiento fue ANSI NULLS, pero poniéndolos on o off hizo ninguna diferencia.

Este es un ejemplo simplificado de un problema más grande que encontramos en nuestra aplicación (el sistema se comprobó en una lista de números - IN(1, 2, etc.)). Reemplazamos este cheque con una clave externa (en lugar de IN) y una nueva restricción de verificación que permitía que cualquiera, tanto nulo como ambos, no fuera nulo; haciendo eso evitó el tercer 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 

Respuesta

11

El resultado de la evaluación de la restricción actual de los valores de 123, NULL es indefinido.

  • ([Code] IS NULL AND [System] IS NULL) evalúa a False
  • ([Code] IS NOT NULL AND [System] IN (1, 2, 3)) evalúa a Undefined

El resultado es Undefined

Check Constraint

restricciones CHECK rechazan los valores que dan como resultado FALSO. Debido a que los valores nulos se evalúan como DESCONOCIDOS, su presencia en expresiones puede anular una restricción.

Debe cambiar su cheque por [System] IN (1, 2, 3) en ISNULL([System], 0) IN (1, 2, 3).

Su restricción de comprobación se convierte entonces en

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

No pondría (Falso) entre paréntesis después de indefinido. Definitivamente no es falso. –

+0

@Damien_The_Unbeliever - Sé a qué se refiere, pero lo he agregado entre corchetes en cuanto a lo que 'undefined' hace hacia el resultado final. Agregaré este comentario a la respuesta. –

+2

Pero lo que ha agregado no es verdad. Si el resultado final de la restricción de verificación es 'DESCONOCIDO', entonces se trata igual que si se evaluara como 'VERDADERO' - eso fue lo que sorprendió al OP. –

13

Bienvenido al maravilloso lógica trivalente de SQL. Como puede o no, el resultado de cualquier comparación estándar a null no es TRUE, o FALSE, sino UNKNOWN.

En una cláusula WHERE, toda la cláusula debe evaluarse como TRUE.

En una restricción CHECK, toda la restricción debe evaluarse como no FALSE.

Así, tenemos:

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

que se convierte (para datos de consulta):

(FALSE AND TRUE) 
OR 
(TRUE AND UNKNOWN) 

Y cualquier operador con un UNKNOWN en un lado o el otro evalúa como UNKNOWN, por lo que el general resultado es UNKNOWN. Que no es FALSE, por lo que la evaluación de la restricción de verificación es correcta.


Si desea System no ser nula, es más claro para mí si se agrega que, como un requisito explícito adicional.

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

Puede parecer un poco extraña la forma en que esto se define, pero es consistente con la forma en que funcionan otras restricciones - por ejemplo, una restricción de clave externa puede tener columnas anulables, y si alguna de esas columnas es nula, no tiene que haber una fila coincidente en la tabla referenciada.

+0

Gracias, ¿tenía la impresión de que configurar ANSI_NULL era una forma de controlar este comportamiento? – DaveShaw

+0

@DaveShaw - Creo que debes configurarlo como 'DESACTIVADO', que puede tener otros efectos secundarios – JNK

+0

@JNK Intenté activar y desactivar, y obtuve los mismos resultados. – DaveShaw

Cuestiones relacionadas