Este problema surgió cuando obtuve diferentes recuentos de registros para lo que pensé que eran consultas idénticas, una usando una not in
where
restricción y la otra a left join
. La tabla en la not in
restricció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 off
provoca que B devuelva un resultado.
NOT IN
a una serie de<> and
cambios el comportamiento semántico de no estar en este conjunto a otra cosa?SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);
produce una fila en lugar del conjunto de resultados vacío que esperaba.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:
El segundo:
El DESCONOCIDO no es lo mismo que FALSO, puede probarlo fácilmente llamando a
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 .
fuente
NOT IN
devuelve 0 registros cuando se compara con un valor desconocidoComo
NULL
se desconoce, unaNOT IN
consulta que contenga aNULL
oNULL
s en la lista de valores posibles siempre devolverá0
registros, ya que no hay forma de asegurarse de que elNULL
valor no sea el valor que se está probando.fuente
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
fuente
El título de esta pregunta al momento de escribir es
Del texto de la pregunta parece que el problema estaba ocurriendo en una
SELECT
consulta SQL DML , en lugar de un SQL DDLCONSTRAINT
.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)
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):
Según la respuesta de @ Brannon, la primera restricción (usando
IN
) se evalúa como VERDADERA y la segunda restricción (usandoNOT 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
En otras palabras:
En SQL DML, las filas se eliminan del resultado cuando se
WHERE
evalú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)!
fuente
NOT IN (subquery)
involucrar nulos puede dar resultados inesperados, es tentador evitarlos porIN (subquery)
completo y usarlos siempreNOT EXISTS (subquery)
(¡como lo hice una vez!) Porque parece que siempre maneja los nulos correctamente. Sin embargo, hay casos en los queNOT IN (subquery)
da el resultado esperado mientras queNOT 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!TRUE
,FALSE
yUNKNOWN
. 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.CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );
- la intención aquí es queb
debe ser iguala
o 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 ejemploCHECK( a = b OR b IS NULL )
. Por lo tanto, cada restricción necesitaría tener una...OR IS NULL
ló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.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.
fuente
Se puede concluir de las respuestas aquí que
NOT IN (subquery)
no maneja nulos correctamente y debe evitarse a favorNOT 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), esNOT IN
que maneja los valores nulos correctamente y devuelve el resultado correcto, en lugar deNOT EXISTS
.Considere una tabla
sp
para representar proveedores (sno
) que se sabe que suministran piezas (pno
) en cantidad (qty
). La tabla actualmente contiene los siguientes valores: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 IN
identifican correctamente al proveedor 'S2':Sin embargo, la consulta a continuación utiliza la misma estructura general pero
NOT EXISTS
incluye incorrectamente al proveedor 'S1' en el resultado (es decir, para el cual la cantidad es nula):¡Entonces
NOT EXISTS
no 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 piezasspq
proveedores conocidos por suministrar piezas en cantidades conocidasseñalando que probablemente debería haber una restricción de clave externa donde las
spq
referenciassp
.El resultado se puede obtener utilizando el operador relacional 'menos' (que es la
EXCEPT
palabra clave en SQL estándar), por ejemplofuente
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.
fuente
SI desea filtrar con NOT IN para una subconsulta que contenga NULL, solo marque para no nulo
fuente
Esto es para Boy:
esto funciona independientemente de la configuración de ansi
fuente
SQL usa lógica de tres valores para los valores de verdad. La
IN
consulta produce el resultado esperado:Pero agregar a
NOT
no invierte los resultados:Esto se debe a que la consulta anterior es equivalente a lo siguiente:
Así es como se evalúa la cláusula where:
Darse cuenta de:
NULL
rendimientosUNKNOWN
OR
expresión donde ninguno de los operandos esTRUE
y al menos un operando esUNKNOWN
rendimientosUNKNOWN
( ref )NOT
deUNKNOWN
rendimientosUNKNOWN
( 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
NULL
entonces, ninguna fila coincidirá.fuente
También esto podría ser útil para conocer la diferencia lógica entre join, exist y en http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
fuente