EXISTE (SELECCIONE 1 ...) vs EXISTE (SELECCIONE * ...) ¿Uno u otro?

38

Siempre que necesito verificar la existencia de alguna fila en una tabla, tiendo a escribir siempre una condición como:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Algunas otras personas lo escriben como:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Cuando la condición es en NOT EXISTSlugar de EXISTS: En algunas ocasiones, podría escribirlo con una LEFT JOINy una condición adicional (a veces llamada antiunión ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

Intento evitarlo porque creo que el significado es menos claro, especialmente cuando lo que es tuyo primary_keyno es tan obvio, o cuando tu clave principal o tu condición de unión es de varias columnas (y puedes olvidar fácilmente una de las columnas). Sin embargo, a veces mantienes el código escrito por otra persona ... y está ahí.

  1. ¿Hay alguna diferencia (aparte del estilo) para usar en SELECT 1lugar de SELECT *?
    ¿Hay algún caso de esquina donde no se comporta de la misma manera?

  2. Aunque lo que escribí es SQL estándar (AFAIK): ¿Hay tanta diferencia para diferentes bases de datos / versiones anteriores?

  3. ¿Hay alguna ventaja en la explicidad de escribir un antijoin?
    ¿Los planificadores / optimizadores contemporáneos lo tratan de manera diferente a la NOT EXISTScláusula?

joanolo
fuente
55
Tenga en cuenta que PostgreSQL admite selecciones sin columnas, por lo que puede escribir EXISTS (SELECT FROM ...).
derecha el
1
He estado haciendo casi la misma pregunta sobre SO hace un par de años: stackoverflow.com/questions/7710153/…
Erwin Brandstetter

Respuestas:

45

No, no hay diferencia en la eficiencia entre (NOT) EXISTS (SELECT 1 ...)y (NOT) EXISTS (SELECT * ...)en todos los DBMS principales. A menudo he visto (NOT) EXISTS (SELECT NULL ...)ser utilizado también.

En algunos incluso puede escribir (NOT) EXISTS (SELECT 1/0 ...)y el resultado es el mismo, sin ningún error (división por cero), lo que demuestra que la expresión allí ni siquiera se evalúa.


Sobre el LEFT JOIN / IS NULLmétodo antijoin, una corrección: esto es equivalente a NOT EXISTS (SELECT ...).

En este caso, NOT EXISTSvsLEFT JOIN / IS NULL, puede obtener diferentes planes de ejecución. En MySQL, por ejemplo, y principalmente en versiones anteriores (anteriores a 5.7), los planes serían bastante similares pero no idénticos. Los optimizadores de otros DBMS (SQL Server, Oracle, Postgres, DB2) son, hasta donde yo sé, más o menos capaces de reescribir estos 2 métodos y considerar los mismos planes para ambos. Aún así, no existe tal garantía y al hacer la optimización, es bueno verificar los planes de diferentes reescrituras equivalentes, ya que podría haber casos en los que cada optimizador no reescribe (por ejemplo, consultas complejas, con muchas combinaciones y / o tablas derivadas / subconsultas dentro de la subconsulta, donde las condiciones de varias tablas, columnas compuestas utilizadas en las condiciones de unión) o las opciones y planes del optimizador se ven afectados de manera diferente por los índices, configuraciones, etc. disponibles

También tenga en cuenta que USINGno se puede usar en todos los DBMS (SQL Server, por ejemplo). El más común JOIN ... ONfunciona en todas partes.
Y las columnas deben tener el prefijo con el nombre / alias de la tabla SELECTpara evitar errores / ambigüedades cuando tenemos uniones.
Por lo general, también prefiero poner la columna unida en el IS NULLcheque (aunque el PK o cualquier columna no anulable estaría bien, podría ser útil para la eficiencia cuando el plan LEFT JOINutiliza un índice no agrupado):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

También hay un tercer método para antijoins, NOT INpero tiene una semántica diferente (¡y resultados!) Si la columna de la tabla interna es anulable. Sin embargo, puede usarse excluyendo las filas con NULL, haciendo que la consulta sea equivalente a las 2 versiones anteriores:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Esto también suele generar planes similares en la mayoría de los DBMS.

ypercubeᵀᴹ
fuente
1
Hasta versiones muy recientes de MySQL [NOT] IN (SELECT ...), aunque equivalentes, se desempeñaron muy mal. ¡Evítalo!
Rick James
44
Esto no es cierto para PostgreSQL . SELECT *Ciertamente está haciendo más trabajo. Yo, por simplicidad, aconsejaría usarSELECT 1
Evan Carroll
11

Hay una categoría de casos donde SELECT 1y SELECT *no son intercambiables, más específicamente, uno siempre será aceptado en esos casos, mientras que el otro no lo será.

Estoy hablando de casos en los que necesita verificar la existencia de filas de un conjunto agrupado . Si la tabla Ttiene columnas C1y C2está verificando la existencia de grupos de filas que coincidan con una condición específica, puede usar SELECT 1así:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

pero no puedes usar SELECT *de la misma manera.

Eso es simplemente un aspecto sintáctico. Cuando ambas opciones se aceptan sintácticamente, lo más probable es que no tenga diferencias en términos de rendimiento o de resultados, como se explicó en la otra respuesta .

Notas adicionales después de los comentarios

Parece que no muchos productos de bases de datos realmente admiten esta distinción. Los productos como SQL Server, Oracle, MySQL y SQLite aceptarán SELECT *con gusto en la consulta anterior sin ningún error, lo que probablemente significa que tratan a los EXISTENTES SELECTde una manera especial.

PostgreSQL es un RDBMS donde SELECT *puede fallar, pero aún puede funcionar en algunos casos. En particular, si está agrupando por PK, SELECT *funcionará bien, de lo contrario fallará con el mensaje:

ERROR: la columna "T.C2" debe aparecer en la cláusula GROUP BY o usarse en una función agregada

Andriy M
fuente
1
Buenos puntos, aunque este no es exactamente el caso que me preocupaba. Este muestra una diferencia conceptual . Porque, cuando tú GROUP BY, el concepto de no *tiene sentido (o, al menos, no está tan claro).
joanolo
5

Una forma posiblemente interesante de reescribir la EXISTScláusula que resulta en una consulta más limpia y quizás menos engañosa, al menos en SQL Server sería:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

La versión anti-semi-join de eso se vería así:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Ambos están típicamente optimizados para el mismo plan que WHERE EXISTSo WHERE NOT EXISTS, pero la intención es inconfundible y no tiene "extraño" 1o *.

Curiosamente, los problemas de verificación nula asociados con NOT IN (...)son problemáticos <> ALL (...), mientras que el NOT EXISTS (...)no sufre ese problema. Considere las siguientes dos tablas con una columna anulable:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Agregaremos algunos datos a ambos, con algunas filas que coinciden y algunas que no:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
El | ID | SomeValue |
+ -------- + ----------- +
El | 1 | 1 |
El | 2 | 2 |
El | 3 | 3 |
El | 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
El | ID | SomeValue |
+ -------- + ----------- +
El | 1 | 1 |
El | 2 | 2 |
El | 3 | NULL |
El | 4 | 4 |
+ -------- + ----------- +

La NOT IN (...)consulta:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Tiene el siguiente plan:

ingrese la descripción de la imagen aquí

La consulta no devuelve filas ya que los valores NULL hacen que la igualdad sea imposible de confirmar.

Esta consulta, con <> ALL (...)muestra el mismo plan y no devuelve filas:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

ingrese la descripción de la imagen aquí

La variante que usa NOT EXISTS (...)muestra una forma de plan ligeramente diferente y devuelve filas:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

El plan:

ingrese la descripción de la imagen aquí

Los resultados de esa consulta:

+ -------- + ----------- +
El | ID | SomeValue |
+ -------- + ----------- +
El | 3 | 3 |
El | 4 | NULL |
+ -------- + ----------- +

Esto hace que el uso sea <> ALL (...)tan propenso a resultados problemáticos como NOT IN (...).

Max Vernon
fuente
3
Debo decir que no me parece *extraño: leo EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... De todos modos, me gusta tener alternativas, y la suya es claramente legible. Una duda / advertencia: ¿cómo se comportará si bes anulable? [He tenido malas experiencias y algunas noches cortas cuando trato de descubrir un error causado por un x IN (SELECT something_nullable FROM a_table)]
joanolo
EXISTS le dice si una tabla tiene una fila y devuelve verdadero o falso. EXISTS (SELECT x FROM (values ​​(null)) es verdadero. IN es = ANY & NOT IN es <> ALL. Estos 4 toman una fila RHS con NULL para que posiblemente coincidan. (X) = ANY (valores (null)) & (x) <> TODOS (valores (nulo)) son desconocidos / nulos pero EXISTE (valores (nulo)) es verdadero. (IN & = CUALQUIERA tiene los mismos "problemas de verificación nula asociados con NOT IN (...) [& ] <> TODOS (...) ". CUALQUIERA Y TODA iteran OR y AND. Pero solo hay" problemas "si no organiza la semántica como se esperaba.) No aconseje usar estos para EXISTOS. Son engañosos , no "menos engañoso"
philipxy
@philliprxy: si me equivoco, no tengo problemas para admitirlo. Siéntase libre de agregar su propia respuesta si lo desea.
Max Vernon
4

La "prueba" de que son idénticos (en MySQL) es hacer

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

luego repite con SELECT 1. En ambos casos, la salida 'extendida' muestra que se transformó en SELECT 1.

Del mismo modo, COUNT(*)se convierte en COUNT(0).

Otra cosa a tener en cuenta: se han realizado mejoras de optimización en versiones recientes. Puede valer la pena comparar EXISTSvs anti-une. Su versión puede hacer un mejor trabajo con uno versus el otro.

Rick James
fuente
4

En algunas bases de datos, esta optimización aún no funciona. Como por ejemplo en PostgreSQL A partir de la versión 9.6, esto fallará.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Y esto tendrá éxito.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Está fallando porque lo siguiente falla pero eso todavía significa que hay una diferencia.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Puede encontrar más información sobre este capricho particular y la violación de la especificación en mi respuesta a la pregunta: ¿La especificación SQL requiere un GROUP BY en EXISTS ()

Evan Carroll
fuente
Un caso raro de esquina, un poco raro tal vez, pero una vez más, una prueba de que usted tiene que hacer un montón de compromisos en el diseño de una base de datos ...
joanolo
-1

Siempre he usado select top 1 'x'(SQL Server)

Teóricamente, select top 1 'x'sería más eficiente que select *, ya que el primero estaría completo después de seleccionar una constante sobre la existencia de una fila de clasificación, mientras que el segundo seleccionaría todo.

SIN EMBARGO, aunque desde el principio puede haber sido relevante, la optimización ha hecho que la diferencia sea irrelevante en probablemente todos los RDBS principales.

G DeMasters
fuente
Tiene sentido. Ese podría ser (o podría haber sido) uno de los pocos casos en los que top nsin order byuna buena idea.
joanolo
3
"Teóricamente ..." No, teóricamente select top 1 'x'no debería ser más eficiente que select *en una Existexpresión. Prácticamente puede ser más eficiente si el optimizador funciona por debajo de lo óptimo, pero en teoría ambas expresiones son equivalentes.
miracle173
-4

IF EXISTS(SELECT TOP(1) 1 FROMes un hábito mejor a largo plazo y en todas las plataformas simplemente porque ni siquiera necesita comenzar a preocuparse acerca de cuán buena o mala es su plataforma / versión actual; y SQL se está moviendo de TOP nhacia parametrizable TOP(n). Esto debería ser una habilidad de aprender una vez.

ajeh
fuente
3
¿Qué quieres decir con "a través de plataformas" ? TOPNi siquiera es válido SQL.
ypercubeᵀᴹ
"SQL se está moviendo ..." es simplemente incorrecto. No existe TOP (n)en "SQL", el lenguaje de consulta estándar. Hay uno en T-SQL que es el dialecto que utiliza Microsoft SQL Server.
a_horse_with_no_name
La etiqueta de la pregunta original es "SQL Server". Pero está bien hacer un voto negativo y disputar lo que dije: el propósito de este sitio es permitir un voto negativo fácil. ¿Quién soy yo para llover en tu desfile con aburrida atención al detalle?
ajeh