¿Cómo funcionan las sentencias SQL EXISTS?

88

Estoy tratando de aprender SQL y me cuesta entender las declaraciones EXISTS. Encontré esta cita sobre "existe" y no entiendo algo:

Usando el operador existe, su subconsulta puede devolver cero, una o muchas filas, y la condición simplemente verifica si la subconsulta devolvió alguna fila. Si observa la cláusula select de la subconsulta, verá que consta de un solo literal (1); Dado que la condición en la consulta contenedora solo necesita saber cuántas filas se han devuelto, los datos reales que devolvió la subconsulta son irrelevantes.

Lo que no entiendo es cómo la consulta externa sabe qué fila está verificando la subconsulta. Por ejemplo:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Entiendo que si la identificación del proveedor y la tabla de pedidos coinciden, la subconsulta devolverá verdadero y se mostrarán todas las columnas de la fila correspondiente en la tabla de proveedores. Lo que no entiendo es cómo la subconsulta comunica qué fila específica (digamos, la fila con la identificación del proveedor 25) debe imprimirse si solo se devuelve verdadero o falso.

Me parece que no hay relación entre la consulta externa y la subconsulta.

Dan
fuente

Respuestas:

98

Piénsalo de esta manera:

Para 'cada' fila de Suppliers, verifique si 'existe' una fila en la Ordertabla que cumpla con la condición Suppliers.supplier_id(esto proviene de la 'fila' actual de la consulta externa) = Orders.supplier_id. Cuando encuentre la primera fila coincidente, deténgase allí mismo: WHERE EXISTSse ha satisfecho.

El vínculo mágico entre la consulta externa y la subconsulta radica en el hecho de que Supplier_idse pasa de la consulta externa a la subconsulta para cada fila evaluada.

O, para decirlo de otra manera, la subconsulta se ejecuta para cada fila de la tabla de la consulta externa.

NO es que la subconsulta se ejecute en su totalidad y obtenga el 'verdadero / falso' y luego intente hacer coincidir esta condición 'verdadero / falso' con la consulta externa.

sojin
fuente
7
¡Gracias! "NO es como una subconsulta ejecutada en su totalidad y obtiene el 'verdadero / falso', y luego intenta hacer coincidir esta condición 'verdadero / falso' con la consulta externa". es lo que realmente me aclaró, sigo pensando que así es como funcionan las subconsultas (y muchas veces lo hacen), pero lo que dijo tiene sentido porque la subconsulta se basa en la consulta externa y, por lo tanto, debe ejecutarse una vez por fila
Clarence Liu
32

Me parece que no hay relación entre la consulta externa y la subconsulta.

¿Qué crees que está haciendo la cláusula WHERE dentro del ejemplo EXISTS? ¿Cómo se llega a esa conclusión cuando la referencia SUPPLIERS no está en las cláusulas FROM o JOIN dentro de la cláusula EXISTS?

EXISTS valora VERDADERO / FALSO y sale como VERDADERO en la primera coincidencia de los criterios; por eso puede ser más rápido que IN. También tenga en cuenta que la cláusula SELECT en un EXISTS se ignora - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... debería llegar a una división por error cero, pero no lo hará. La cláusula WHERE es la parte más importante de una cláusula EXISTS.

También tenga en cuenta que JOIN no es un reemplazo directo de EXISTS, porque habrá registros principales duplicados si hay más de un registro secundario asociado al principal.

Ponis dios mio
fuente
1
Todavía me falta algo. Si sale en la primera coincidencia, ¿cómo es que la salida termina siendo todos los resultados donde o.supplierid = s.supplierid? ¿No generaría simplemente el primer resultado en su lugar?
Dan
3
@Dan: Las EXISTSsalidas, devolviendo VERDADERO en la primera coincidencia, porque el proveedor existe al menos una vez en la tabla PEDIDOS. Si quisiera ver la duplicación de los datos del PROVEEDOR debido a que tiene más de una relación secundaria en PEDIDOS, tendría que usar JOIN. Pero la mayoría no quiere esa duplicación, y la ejecución de GROUP BY / DISTINCT puede agregar una sobrecarga a la consulta. EXISTSes más eficiente que SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...en SQL Server, no he probado en Oracle o MySQL últimamente.
OMG Ponies
Tenía una pregunta, ¿se hace la coincidencia para cada registro que está SELECCIONADO en la consulta externa? Al igual que en, obtenemos de Pedidos 5 veces si hay 5 filas seleccionadas de Proveedores.
Rahul Kadukar
24

Puede producir resultados idénticos usando JOIN, EXISTS, IN, o INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
fuente
1
gran respuesta, pero también tenga en cuenta que es mejor no usar existe para evitar la correlación
Florian Fröhlich
1
¿Qué consulta cree que se ejecutará más rápido si los proveedores tienen 10 millones de filas y los pedidos tienen 100 millones de filas y por qué?
Teja
7

Si tuviera una cláusula where que se pareciera a esto:

WHERE id in (25,26,27) -- and so on

puede comprender fácilmente por qué se devuelven algunas filas y otras no.

Cuando la cláusula where es así:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

simplemente significa: devolver filas que tienen un registro existente en la tabla de pedidos con la misma identificación.

Menahem
fuente
2

Esta es una muy buena pregunta, así que decidí escribir un artículo muy detallado sobre este tema en mi blog.

Modelo de tabla de base de datos

Supongamos que tenemos las siguientes dos tablas en nuestra base de datos, que forman una relación de tabla de uno a muchos.

Tablas SQL EXISTS

La studenttabla es la principal y la student_gradetabla secundaria, ya que tiene una columna de clave externa student_id que hace referencia a la columna de clave principal id en la tabla de estudiantes.

El student tablecontiene los siguientes dos registros:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

Y la student_gradetabla almacena las calificaciones que recibieron los estudiantes:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTE

Digamos que queremos que todos los estudiantes que hayan recibido una calificación de 10 en la clase de matemáticas.

Si solo estamos interesados ​​en el identificador del estudiante, entonces podemos ejecutar una consulta como esta:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Pero, la aplicación está interesada en mostrar el nombre completo de a student, no solo el identificador, por lo que también necesitamos información de la studenttabla.

Para filtrar los studentregistros que tienen una nota de 10 en Matemáticas, podemos usar el operador EXISTS SQL, así:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Al ejecutar la consulta anterior, podemos ver que solo se selecciona la fila Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

La consulta externa selecciona las studentcolumnas de fila que estamos interesados ​​en devolver al cliente. Sin embargo, la cláusula WHERE utiliza el operador EXISTS con una subconsulta interna asociada.

El operador EXISTS devuelve verdadero si la subconsulta devuelve al menos un registro y falso si no se selecciona ninguna fila. El motor de la base de datos no tiene que ejecutar la subconsulta por completo. Si un solo registro coincide, el operador EXISTS devuelve verdadero y se selecciona la otra fila de consulta asociada.

La subconsulta interna está correlacionada porque la columna student_id de la student_gradetabla se compara con la columna id de la tabla externa de estudiantes.

Vlad Mihalcea
fuente
Qué gran respuesta. Creo que no entendí el concepto porque estaba usando un ejemplo equivocado. ¿ EXISTSolo funciona con subconsultas correlacionadas? Estaba jugando con una consulta que contenía solo 1 tabla, como SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Sé que lo que escribí podría lograrse con una simple consulta DONDE, pero solo la estaba usando para comprender EXISTS. Tengo todas las filas. ¿De hecho se debe al hecho de que no utilicé subconsultas correlacionadas? Gracias.
Bowen Liu
Tiene sentido solo para subconsultas correlacionadas, ya que desea filtrar los registros de la consulta externa. En su caso, la consulta interna se puede reemplazar por DONDE ES VERDADERO
Vlad Mihalcea
Gracias Vlad. Eso es lo que pensé. Es una idea extraña que se me ocurrió cuando estaba jugando con eso. Honestamente, no conocía el concepto de subconsulta correlacionada. Y ahora tiene mucho más sentido filtrar las filas de la consulta externa con la consulta interna.
Bowen Liu
0

EXISTS significa que la subconsulta devuelve al menos una fila, eso es realmente. En ese caso, es una subconsulta correlacionada porque verifica el proveedor_id de la tabla externa con el proveedor_id de la tabla interna. Esta consulta dice, en efecto:

SELECCIONAR todos los proveedores Para cada ID de proveedor, vea si existe un pedido para este proveedor Si el proveedor no está presente en la tabla de pedidos, elimine al proveedor de los resultados VOLVER a todos los proveedores que tienen filas correspondientes en la tabla de pedidos

Puede hacer lo mismo en este caso con INNER JOIN.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

El comentario de Ponies es correcto. Debería hacer agrupaciones con esa combinación o seleccionar distintas según los datos que necesite.

David Fells
fuente
4
La combinación interna producirá resultados diferentes a EXISTS si hay más de un registro secundario asociado con un padre; no son idénticos.
OMG Ponies
Creo que mi confusión podría ser que he leído que la subconsulta con EXISTS devuelve verdadero o falso; pero esto no puede ser lo único que regrese, ¿verdad? ¿La subconsulta también devuelve todos los "proveedores que tienen filas correspondientes en la tabla de pedidos"? Pero si es así, ¿cómo la declaración EXISTS devuelve un resultado booleano? Todo lo que leo en los libros de texto dice que solo devuelve un resultado booleano, por lo que me cuesta conciliar el resultado del código con lo que me dicen que devuelve.
Dan
Lea EXISTS como una función ... EXISTS (conjunto de resultados). La función EXISTS devolvería verdadero si el conjunto de resultados tiene filas, falso si está vacío. Eso es básicamente todo.
David Fells
3
@Dan, considere que EXISTS () se evalúa lógicamente para cada fila de origen de forma independiente; no es un valor único para toda la consulta.
Arvo
-1

Lo que describe es una consulta con una subconsulta correlacionada .

(En general) es algo que debe intentar evitar escribiendo la consulta usando una combinación en su lugar:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Porque de lo contrario, la subconsulta se ejecutará para cada fila de la consulta externa.

Wouter van Nifterick
fuente
2
Esas dos soluciones no son equivalentes. JOIN da un resultado diferente al de la subconsulta EXISTS si hay más de una fila ordersque coincide con la condición de unión.
a_horse_with_no_name
1
gracias por la solución alternativa. pero, ¿sugiere que si se le da una opción entre la subconsulta correlacionada y la combinación, debería ir con la combinación porque es más eficiente?
sunny_dev