SQL "seleccionar donde no está en subconsulta" no devuelve ningún resultado

130

Descargo de responsabilidad: he resuelto el problema (creo), pero quería agregar este problema a Stack Overflow ya que no podía (fácilmente) encontrarlo en ningún lado. Además, alguien podría tener una mejor respuesta que yo.

Tengo una base de datos donde varias tablas hacen referencia a una tabla "Común". Quería ver qué registros de la tabla común quedaron huérfanos (es decir, no tenía referencias de ninguna de las otras tablas).

Ejecuté esta consulta:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

Sé que hay registros huérfanos, pero no se devolvieron registros. Por qué no?

(Esto es SQL Server, si es importante).

Jeremy Stein
fuente
Este stackoverflow.com/a/129152/1667619 responde bastante bien a la pregunta POR QUÉ.
Ruchan

Respuestas:

234

Actualizar:

Estos artículos en mi blog describen las diferencias entre los métodos con más detalle:


Hay tres formas de hacer una consulta de este tipo:

  • LEFT JOIN / IS NULL:

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
  • NOT EXISTS:

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
  • NOT IN:

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )

Cuando table1.common_idno es anulable, todas estas consultas son semánticamente iguales.

Cuando es anulable, NOT INes diferente, ya que IN(y, por lo tanto NOT IN) regresa NULLcuando un valor no coincide con nada en una lista que contiene un NULL.

Esto puede ser confuso pero puede volverse más obvio si recordamos la sintaxis alternativa para esto:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

El resultado de esta condición es un producto booleano de todas las comparaciones dentro de la lista. Por supuesto, un solo NULLvalor produce el NULLresultado que también representa el resultado completo NULL.

Nunca podemos decir definitivamente que common_idno es igual a nada de esta lista, ya que al menos uno de los valores lo es NULL.

Supongamos que tenemos estos datos:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLy NOT EXISTSvolverá 3, NOT INvolverá nada (ya que siempre evaluará a cualquiera FALSEo NULL).

En MySQL, en el caso de la columna no anulable, LEFT JOIN / IS NULLy NOT INson un poco (varios por ciento) más eficientes que NOT EXISTS. Si la columna es anulable, NOT EXISTSes la más eficiente (nuevamente, no mucho).

En Oracle, las tres consultas producen los mismos planes (an ANTI JOIN).

En SQL Server, NOT IN/ NOT EXISTSson más eficientes, ya LEFT JOIN / IS NULLque no puede ser optimizado para un ANTI JOINpor su optimizador.

En PostgreSQL, LEFT JOIN / IS NULLy NOT EXISTSson más eficientes que NOT IN, sinusoidales, están optimizados para un Anti Join, mientras que los NOT INusos hashed subplan(o incluso un plano subplansi la subconsulta es demasiado grande para el hash)

Quassnoi
fuente
8
¡Gran respuesta! ¡Gracias!
StevenMcD
esto es increíble y muy útil
kavun
1
¡+1 porque, cuatro años y medio después, esta respuesta me ayudó con un problema que me dejó perplejo!
Carson63000
@ Carson63000 Snap! Pensé que me estaba volviendo loco antes de ver esta respuesta
Bobby
1
@IstiaqueAhmed: se NOT EXISTSevalúa como VERDADERO si la consulta en su interior devuelve alguna fila. SELECT NULLbien podría ser SELECT *o SELECT 1cualquier otra cosa, el NOT EXISTSpredicado no mira los valores de las filas, solo los cuenta.
Quassnoi
36

Si desea que el mundo sea un lugar booleano de dos valores, debe evitar el caso nulo (tercer valor) usted mismo.

No escriba cláusulas IN que permitan nulos en el lado de la lista. ¡Filtralos!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Amy B
fuente
66
los nulos en la lista de cláusulas son una razón común para la falta de resultados de la consulta.
Amy B
'Cuando se compara con un valor nulo, la respuesta es desconocida', de la respuesta de @Jeremy Stein. De common_id not in, todavía podemos tener common_idvalor que es NULL. Entonces, ¿el problema de no obtener resultados aún persiste?
Istiaque Ahmed
5

Table1 o Table2 tiene algunos valores nulos para common_id. Utilice esta consulta en su lugar:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
Jeremy Stein
fuente
1
¿Qué pasa si hay datos en una tabla pero no en la otra? ¿Quieres "y" o "o" allí?
Philip Kelley
1
Estoy buscando registros no referenciados en ninguna tabla, por lo que quiero AND. Aclararé la pregunta.
Jeremy Stein
4
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
Patmortech
fuente
4

Justo al lado de la parte superior de mi cabeza...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

Hice algunas pruebas y aquí estaban mis resultados con la respuesta de wrt @ patmortech y los comentarios de @ rexem.

Si Table1 o Table2 no están indexados en commonID, obtiene un escaneo de la tabla, pero la consulta de @ patmortech sigue siendo el doble de rápida (para una tabla maestra de filas de 100K).

Si ninguno de ellos está indexado en commonID, obtiene dos escaneos de tabla y la diferencia es insignificante.

Si ambos están indexados en commonID, la consulta "no existe" se ejecuta en 1/3 del tiempo.

Austin Salonen
fuente
1
Eso debería ser un AND en la cláusula where. De lo contrario, eso funciona.
Jeremy Stein
1
cambiado por su comentario. El "o" selecciona huérfanos en cualquier tabla.
Austin Salonen
1
Eso es mejor. Por cierto, ¿hay alguna razón por la que debería usar combinaciones externas en lugar de la subconsulta?
Jeremy Stein
3
La legibilidad es la principal. Sospecho que se generaría un mejor plan de ejecución, pero sin un plan de consulta, no puedo confirmar.
Austin Salonen
2
Este enfoque es peor que el uso de NO EXISTE: la unión da como resultado la obtención de más filas de las que necesita, luego los resultados comparados para las columnas son nulos. Y NO EXISTE es más legible para arrancar.
OMG Ponies
3
SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL
manji
fuente
1
Este enfoque es peor que el uso de NO EXISTE: la unión da como resultado la obtención de más filas de las que necesita, luego los resultados comparados para las columnas son nulos. Funciona, pero el rendimiento no será tan bueno, posiblemente peor que usar IN con subconsultas correlacionadas.
OMG Ponies
3

Supongamos que estos valores para common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Queremos que la fila en común regrese, porque no existe en ninguna de las otras tablas. Sin embargo, el nulo arroja una llave inglesa.

Con esos valores, la consulta es equivalente a:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

Eso es equivalente a:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

Aquí es donde empieza el problema. Cuando se compara con un nulo, la respuesta es desconocida . Entonces la consulta se reduce a

select *
from Common
where not (false)
and not (false or unkown)

falso o desconocido es desconocido:

select *
from Common
where true
and not (unknown)

verdadero y no desconocido también es desconocido:

select *
from Common
where unknown

La condición where no devuelve registros donde el resultado es desconocido, por lo que no recuperamos registros.

Una forma de lidiar con esto es usar el operador exist en lugar de in. Exists nunca devuelve desconocido porque opera en filas en lugar de columnas. (Existe una fila o no existe; ¡nada de esta ambigüedad nula a nivel de fila!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)
Jeremy Stein
fuente
2

esto funcionó para mí :)

seleccione * de Común

dónde

common_id no está en (seleccione ISNULL (common_id, 'dummy-data') de Table1)

y common_id no en (seleccione ISNULL (common_id, 'dummy-data') de Table2)

arqueado
fuente
@marlar, las subconsultas siempre devuelven 1 o 0, no una lista de valores. Entonces, ¿cómo va a NOT INactuar allí?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Donga jayesh
fuente
0

Tenía un ejemplo en el que estaba buscando y debido a que una tabla contenía el valor como un doble, la otra como una cadena, no coincidirían (o no coincidirían sin un molde). Pero solo NO EN . Como SELECT ... IN ... funcionó. Extraño, pero pensé que compartiría en caso de que alguien más se encuentre con esta solución simple.

ransems
fuente
0

Siga el siguiente ejemplo para comprender el tema anterior:

También puedes visitar el siguiente enlace para saber Anti unirse

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

Pero si usamos NOT INen ese caso no obtenemos ningún dato.

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

datos no encontrados

Esto sucede cuando ( select department_id from hr.employees) devuelve un valor nulo y toda la consulta se evalúa como falsa. Podemos verlo si cambiamos el SQL ligeramente como se muestra a continuación y manejamos valores nulos con la función NVL.

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

Ahora estamos obteniendo datos:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

Nuevamente estamos obteniendo datos ya que hemos manejado el valor nulo con la función NVL.

Rajesh Sarkar
fuente
Los resultados de SQl no aparecen en forma de tabla, por favor conténteme.
Rajesh Sarkar