Valores NULL dentro de la cláusula NOT IN

245

Este problema surgió cuando obtuve diferentes recuentos de registros para lo que pensé que eran consultas idénticas, una usando una not in whererestricción y la otra a left join. La tabla en la not inrestricción tenía un valor nulo (datos incorrectos) que hizo que esa consulta devolviera un recuento de 0 registros. Entiendo por qué, pero podría necesitar algo de ayuda para comprender completamente el concepto.

Para decirlo simplemente, ¿por qué la consulta A devuelve un resultado pero B no?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

Esto fue en SQL Server 2005. También descubrí que llamar set ansi_nulls offprovoca que B devuelva un resultado.

Jamie Ide
fuente

Respuestas:

283

La consulta A es igual a:

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

Como 3 = 3es verdad, obtienes un resultado.

La consulta B es lo mismo que:

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

Cuando ansi_nullsestá 3 <> nullactivado , es DESCONOCIDO, por lo que el predicado se evalúa como DESCONOCIDO y no obtiene ninguna fila.

Cuando ansi_nullsestá desactivado, 3 <> nulles verdadero, por lo que el predicado se evalúa como verdadero y obtienes una fila.

Brannon
fuente
11
¿Alguien ha señalado que convertir NOT INa una serie de <> andcambios el comportamiento semántico de no estar en este conjunto a otra cosa?
Ian Boyd el
8
@Ian - Parece que "A NOT IN ('X', 'Y')" en realidad es un alias para A <> 'X' AND A <> 'Y' en SQL. (Veo que descubrió esto usted mismo en stackoverflow.com/questions/3924694/… , pero quería asegurarse de que su objeción fuera abordada en esta pregunta).
Ryan Olson,
Supongo que esto explica por qué SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);produce una fila en lugar del conjunto de resultados vacío que esperaba.
binki
2
Este es un comportamiento muy pobre del servidor SQL, porque si espera una comparación NULL usando "IS NULL", debería expandir la cláusula IN al mismo comportamiento y no aplicar estúpidamente la semántica incorrecta a sí mismo.
OzrenTkalcecKrznaric
@binki, Tu consulta se ejecuta si se ejecuta aquí rextester.com/l/sql_server_online_compiler pero no funciona si se ejecuta aquí sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed
53

Cada vez que usa NULL, realmente se trata de una lógica de tres valores.

Su primera consulta devuelve resultados a medida que la cláusula WHERE se evalúa como:

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

El segundo:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

El DESCONOCIDO no es lo mismo que FALSO, puede probarlo fácilmente llamando a

select 'true' where 3 <> null
select 'true' where not (3 <> null)

Ambas consultas no le darán resultados.

Si el DESCONOCIDO fuera igual a FALSO, suponiendo que la primera consulta le daría FALSO, la segunda tendría que evaluar como VERDADERO, ya que habría sido lo mismo que NO (FALSO).
Ese no es el caso.

Hay un muy buen artículo sobre este tema en SqlServerCentral .

Todo el asunto de NULLs y Three-Valued Logic puede ser un poco confuso al principio, pero es esencial comprenderlo para escribir consultas correctas en TSQL

Otro artículo que recomendaría es Funciones agregadas de SQL y NULL .

kristof
fuente
33

NOT IN devuelve 0 registros cuando se compara con un valor desconocido

Como NULLse desconoce, una NOT INconsulta que contenga a NULLo NULLs en la lista de valores posibles siempre devolverá 0registros, ya que no hay forma de asegurarse de que el NULLvalor no sea el valor que se está probando.

YonahW
fuente
3
Esta es la respuesta en pocas palabras. Encontré que esto es más fácil de entender, incluso sin ningún ejemplo.
Govind Rai
18

Comparar con nulo no está definido, a menos que use IS NULL.

Entonces, al comparar 3 con NULL (consulta A), devuelve undefined.

Es decir, SELECCIONAR 'verdadero' donde 3 en (1,2, nulo) y SELECCIONAR 'verdadero' donde 3 no en (1,2, nulo)

producirá el mismo resultado, ya que NOT (UNDEFINED) aún no está definido, pero no es VERDADERO

Sunny Milenov
fuente
Gran punto seleccione 1 donde nulo en (nulo) no devuelve filas (ansi).
crokusek
9

El título de esta pregunta al momento de escribir es

SQL NO EN restricción y valores NULL

Del texto de la pregunta parece que el problema estaba ocurriendo en una SELECTconsulta SQL DML , en lugar de un SQL DDL CONSTRAINT.

Sin embargo, especialmente teniendo en cuenta la redacción del título, quiero señalar que algunas declaraciones hechas aquí son declaraciones potencialmente engañosas, a lo largo de las líneas de (parafraseando)

Cuando el predicado se evalúa como DESCONOCIDO, no obtiene ninguna fila.

Aunque este es el caso de SQL DML, al considerar las restricciones, el efecto es diferente.

Considere esta tabla muy simple con dos restricciones tomadas directamente de los predicados en la pregunta (y abordadas en una excelente respuesta por @Brannon):

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

Según la respuesta de @ Brannon, la primera restricción (usando IN) se evalúa como VERDADERA y la segunda restricción (usando NOT IN) se evalúa como DESCONOCIDA. Sin embargo , la inserción tiene éxito! Por lo tanto, en este caso no es estrictamente correcto decir "no se obtienen filas" porque, de hecho, hemos insertado una fila.

El efecto anterior es de hecho el correcto con respecto al estándar SQL-92. Compare y contraste la siguiente sección de la especificación SQL-92

7.6 cláusula where

El resultado de la es una tabla de esas filas de T para las cuales el resultado de la condición de búsqueda es verdadero.

4.10 Restricciones de integridad

Se cumple una restricción de verificación de tabla si y solo si la condición de búsqueda especificada no es falsa para cualquier fila de una tabla.

En otras palabras:

En SQL DML, las filas se eliminan del resultado cuando se WHEREevalúa como DESCONOCIDO porque no cumple la condición "es verdadera".

En SQL DDL (es decir, restricciones), las filas no se eliminan del resultado cuando evalúan a DESCONOCIDO porque no satisface la condición "no es falsa".

Aunque los efectos en SQL DML y SQL DDL respectivamente pueden parecer contradictorios, hay una razón práctica para dar a los resultados DESCONOCIDOS el 'beneficio de la duda' al permitirles satisfacer una restricción (más correctamente, lo que les permite no dejar de cumplir una restricción) : sin este comportamiento, todas las restricciones tendrían que manejar explícitamente los valores nulos y eso sería muy insatisfactorio desde la perspectiva del diseño del lenguaje (¡sin mencionar, un dolor adecuado para los codificadores!)

ps: si le resulta difícil seguir una lógica como "lo desconocido no deja de cumplir una restricción" como lo estoy para escribirlo, entonces considere que puede prescindir de todo esto simplemente evitando columnas anulables en SQL DDL y cualquier cosa en SQL ¡DML que produce nulos (por ejemplo, uniones externas)!

un día cuando
fuente
Sinceramente, no creía que hubiera nada más que decir sobre este tema. Interesante.
Jamie Ide
2
@Jamie Ide: En realidad, tengo otra respuesta sobre el tema: porque NOT IN (subquery)involucrar nulos puede dar resultados inesperados, es tentador evitarlos por IN (subquery)completo y usarlos siempre NOT EXISTS (subquery)(¡como lo hice una vez!) Porque parece que siempre maneja los nulos correctamente. Sin embargo, hay casos en los que NOT IN (subquery)da el resultado esperado mientras que NOT EXISTS (subquery)da resultados inesperados. Puedo llegar a escribir esto todavía si puedo encontrar mis notas sobre el tema (¡necesito notas porque no es intuitivo!) Sin embargo, la conclusión es la misma: ¡evite los valores nulos!
día
@onedaycuando estoy confundido por su afirmación de que NULL necesitaría una carcasa especial para tener un comportamiento consistente (internamente consistente, no consistente con la especificación). ¿No sería suficiente cambiar 4.10 para leer "Una restricción de verificación de tabla se cumple si y solo si la condición de búsqueda especificada es verdadera"?
DylanYoung
@DylanYoung: No, la especificación está redactada de esa manera por una razón crucial: SQL sufre de lógica de tres valores, dónde están esos valores TRUE, FALSEy UNKNOWN. Supongo que 4.10 podría haber leído: "Se cumple una restricción de verificación de tabla si y solo si la condición de búsqueda especificada es VERDADERA o DESCONOCIDA para cada fila de una tabla" - observe el cambio al final de la oración - que usted omitió - - de "para cualquiera" a "para todos". Siento la necesidad de capitalizar los valores lógicos porque el significado de "verdadero" y "falso" en el lenguaje natural seguramente debe referirse a la lógica clásica de dos valores.
día
1
Considere: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- la intención aquí es que bdebe ser igual ao nula. Si una restricción tuviera que resultar VERDADERO para ser satisfecha, entonces tendríamos que cambiar la restricción para manejar explícitamente los valores nulos, por ejemplo CHECK( a = b OR b IS NULL ). Por lo tanto, cada restricción necesitaría tener una ...OR IS NULLlógica agregada por el usuario para cada columna anulable involucrada: más complejidad, más errores cuando se olvidaron de hacerlo, etc. Así que creo que el comité de estándares de SQL solo trató de ser pragmático.
cuando el
7

En A, 3 se prueba para determinar la igualdad frente a cada miembro del conjunto, produciendo (FALSO, FALSO, VERDADERO, DESCONOCIDO). Como uno de los elementos es VERDADERO, la condición es VERDADERA. (También es posible que se produzca un cortocircuito aquí, por lo que en realidad se detiene tan pronto como llega al primer VERDADERO y nunca evalúa 3 = NULO).

En B, creo que está evaluando la condición como NO (3 en (1,2, nulo)). Prueba 3 para la igualdad frente a los rendimientos establecidos (FALSO, FALSO, DESCONOCIDO), que se agrega a DESCONOCIDO. NO (DESCONOCIDO) produce DESCONOCIDO. Entonces, en general, se desconoce la verdad de la condición, que al final se trata esencialmente como FALSA.

Dave Costa
fuente
7

Se puede concluir de las respuestas aquí que NOT IN (subquery)no maneja nulos correctamente y debe evitarse a favor NOT EXISTS. Sin embargo, tal conclusión puede ser prematura. En el siguiente escenario, acreditado a Chris Date (Database Programming and Design, Vol 2 No 9, septiembre de 1989), es NOT INque maneja los valores nulos correctamente y devuelve el resultado correcto, en lugar de NOT EXISTS.

Considere una tabla sppara representar proveedores ( sno) que se sabe que suministran piezas ( pno) en cantidad ( qty). La tabla actualmente contiene los siguientes valores:

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

Tenga en cuenta que la cantidad es anulable, es decir, para poder registrar el hecho de que se sabe que un proveedor suministra piezas, incluso si no se sabe en qué cantidad.

La tarea es encontrar los proveedores que son conocidos con el número de parte de suministro 'P1' pero no en cantidades de 1000.

Los siguientes usos solo NOT INidentifican correctamente al proveedor 'S2':

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

Sin embargo, la consulta a continuación utiliza la misma estructura general pero NOT EXISTSincluye incorrectamente al proveedor 'S1' en el resultado (es decir, para el cual la cantidad es nula):

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

¡Entonces NOT EXISTSno es la bala de plata que pudo haber aparecido!

Por supuesto, la fuente del problema es la presencia de nulos, por lo tanto, la solución 'real' es eliminar esos nulos.

Esto se puede lograr (entre otros diseños posibles) usando dos tablas:

  • sp proveedores conocidos por suministrar piezas
  • spq proveedores conocidos por suministrar piezas en cantidades conocidas

señalando que probablemente debería haber una restricción de clave externa donde las spqreferencias sp.

El resultado se puede obtener utilizando el operador relacional 'menos' (que es la EXCEPTpalabra clave en SQL estándar), por ejemplo

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;
un día cuando
fuente
1
Dios mio. Gracias por escribir esto ... esto me estaba volviendo loco ...
Govind Rai
6

Nulo significa y ausencia de datos, es decir, se desconoce, no es un valor de datos de nada. Es muy fácil para las personas de un entorno de programación confundir esto porque en los lenguajes de tipo C cuando se usan punteros nulos no es nada.

Por lo tanto, en el primer caso, 3 está realmente en el conjunto de (1,2,3, nulo) por lo que se devuelve verdadero

En el segundo, sin embargo, puede reducirlo a

seleccione 'verdadero' donde 3 no está en (nulo)

Por lo tanto, no se devuelve nada porque el analizador no sabe nada sobre el conjunto con el que lo está comparando: no es un conjunto vacío sino un conjunto desconocido. El uso de (1, 2, nulo) no ayuda porque el conjunto (1,2) es obviamente falso, pero entonces estás y eso en contra de desconocido, que es desconocido.

Cruachan
fuente
6

SI desea filtrar con NOT IN para una subconsulta que contenga NULL, solo marque para no nulo

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Mihai
fuente
Tuve un problema con la consulta de combinación externa que no devolvió ningún registro en situaciones especiales, así que verifiqué esta solución para el escenario de registros nulos y existentes y funcionó para mí. Si ocurrieran otros problemas, seré mencionado aquí, muchas gracias.
QMaster
1

Esto es para Boy:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

esto funciona independientemente de la configuración de ansi

CB
fuente
para la pregunta original: B: seleccione 'verdadero' donde 3 no está en (1, 2, nulo) se debe hacer una forma de eliminar los nulos, por ejemplo, seleccione 'verdadero' donde 3 no está en (1, 2, nulo (nulo, 0) ) la lógica general es, si NULL es la causa, busque una forma de eliminar valores NULL en algún paso de la consulta.
seleccione party_code de abc como donde party_code no está dentro (seleccione party_code de xyz donde party_code no es nulo) pero buena suerte si olvidó que el campo permite nulos, que a menudo es el caso
1

SQL usa lógica de tres valores para los valores de verdad. La INconsulta produce el resultado esperado:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

Pero agregar a NOTno invierte los resultados:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

Esto se debe a que la consulta anterior es equivalente a lo siguiente:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

Así es como se evalúa la cláusula where:

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

Darse cuenta de:

  1. La comparación que implica NULLrendimientosUNKNOWN
  2. La ORexpresión donde ninguno de los operandos es TRUEy al menos un operando es UNKNOWNrendimientos UNKNOWN( ref )
  3. El NOTde UNKNOWNrendimientos UNKNOWN( ref )

Puede ampliar el ejemplo anterior a más de dos valores (por ejemplo, NULL, 1 y 2) pero el resultado será el mismo: si uno de los valores es NULLentonces, ninguna fila coincidirá.

Salman A
fuente