2009-09-10 14 views
100

Descargo de responsabilidad: He descubierto el problema (creo), pero quería agregar este problema a Stack Overflow ya que no pude encontrarlo (con facilidad) en ningún lado. Además, alguien podría tener una mejor respuesta que yo.SQL "seleccione dónde no está en la subconsulta" no devuelve ningún resultado

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

que corrió 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 se han devuelto registros. Por qué no?

(Esto es SQL Server, si importa.)

+0

Este https://stackoverflow.com/a/129152/1667619 responde bastante bien a la pregunta WHY. – Ruchan

Respuesta

186

Actualización:

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


Hay tres maneras de hacer este tipo de consulta:

  • 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_id no es anulable, todas estas consultas son semánticamente el mismo.

Cuando es anulable, NOT IN es diferente, ya que IN (y, por lo tanto, NOT IN) volver NULL cuando un valor no coincide con cualquier cosa en una lista que contiene una NULL.

Esto puede ser confuso, pero puede llegar a ser más evidente 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 único valor NULL produce el resultado NULL que representa el resultado completo NULL también.

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

Supongamos que tenemos estos datos:

common 

-- 
1 
3 

table1 

-- 
NULL 
1 
2 

LEFT JOIN/IS NULL y NOT EXISTS volverá 3, NOT IN volverá nada (ya que siempre se evaluará como sea FALSE o NULL).

En MySQL, en caso de que en la columna no anulable, LEFT JOIN/IS NULL y NOT IN son un poco (varios por ciento) más eficiente que NOT EXISTS. Si la columna es nulable, NOT EXISTS es la más eficiente (nuevamente, no mucho).

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

En SQL Server, NOT IN/NOT EXISTS son más eficientes, ya que LEFT JOIN/IS NULL no puede ser optimizado para un ANTI JOIN por su optimizador.

En PostgreSQL, LEFT JOIN/IS NULL y NOT EXISTS son más eficientes que NOT IN, sine que están optimizados para un Anti Join, mientras NOT IN utiliza hashed subplan (o incluso una llanura subplan si la subconsulta es demasiado grande para hash)

+7

¡Gran respuesta! ¡Gracias! – StevenMcD

+0

¡Gran explicación! – lud0h

+0

esto es increíble y muy útil – kavun

4

Tabla 1 o Tabla 2 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) 
+1

¿Qué pasa si hay datos en una tabla pero no en la otra? ¿Quieres "y" o "o" allí? –

+1

Estoy buscando registros no referenciados en ninguna tabla, entonces quiero AND. Voy a aclarar la pregunta. –

3

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 

Me corrieron algunas pruebas, y aquí fueron mis resultados w.r.t. la respuesta de @patmortech y los comentarios de @rexem.

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

Si ninguno de los dos está indexado en commonID, obtendrá 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.

+1

Eso debería ser un AND en la cláusula where. De lo contrario, eso funciona. –

+1

cambiado según tu comentario. El "o" selecciona huérfanos en cualquier tabla. –

+1

Eso es mejor. Por cierto, ¿hay alguna razón por la que deba usar uniones externas en lugar de la subconsulta? –

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 
+1

Este enfoque es peor que el uso de NOT EXISTS: la combinación da como resultado la obtención de más filas de las que necesita, y luego los resultados comparados para las columnas son nulas. Funciona, pero el rendimiento no será tan bueno, posiblemente peor que usar IN con subconsultas correlacionadas. –

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) 
+0

+1 - vea mi respuesta para saber por qué. –

30

Si quieres que el mundo sea un lugar booleana de dos valores, se debe evitar que la hipótesis nula (tercer valor) caja por su cuenta.

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 
) 
+0

Ha entendido mal la pregunta - vuelva a leer. –

+5

Los nulos en la lista de cláusulas son una razón común para perder los resultados de la consulta. –

+3

En realidad, creo que David B entendió la pregunta mejor que nadie. Quería saber POR QUÉ no se devolvieron los resultados. Upvoted. –

3

Supongamos estos valores para common_id:

Common - 1 
Table1 - 2 
Table2 - 3, null 

Queremos la fila en común para regresar, ya que no existe en ninguna de las otras mesas. 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. Al comparar con un valor nulo, the answer is unknown.Por lo que la consulta se reduce a

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

falsa o desconocido es desconocido:

select * 
from Common 
where true 
and not (unknown) 

verdad y no desconocido también es desconocido:

select * 
from Common 
where unknown 

la condición WHERE no devuelve registros en los que el resultado no se conoce, por lo que no recibimos ningún registro.

Una forma de lidiar con esto es usar el operador exists en lugar de in. Exists nunca devuelve desconocido porque opera en filas en lugar de columnas. (Una fila existe o no es así, ninguno de esta ambigüedad nula al 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) 
2

este trabajó para me :)

sELECT * FROM Común

donde

common_id no en (seleccione ISNULL (common_id, 'maniquí-data') de la Tabla1)

y common_id no en (seleccione ISNULL (common_id, 'maniquí-data') de Tabla2)

+0

Solución muy fácil. ¡Gracias! – marlar

+0

@marlar, las consultas secundarias siempre devuelven 1 o 0, no una lista de valores. Entonces, ¿cómo funcionará el 'NOT IN' allí? –

0
select *, 
(select COUNT(ID) from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster 
Cuestiones relacionadas