EXISTS
El operador EXISTS comprueba si una subconsulta devuelve al menos una fila. No le importa qué columnas o valores contiene el resultado, solo si hay filas o no. Devuelve TRUE si la subconsulta tiene resultados y FALSE si está vacía.
EXISTS se usa casi siempre con subconsultas correlacionadas y es especialmente eficiente porque MySQL puede dejar de buscar en cuanto encuentra la primera fila que cumple la condición.
Sintaxis
SELECT columnas
FROM tabla
WHERE EXISTS (subconsulta);La subconsulta dentro de EXISTS es normalmente correlacionada: hace referencia a la consulta exterior. Por convención, se escribe SELECT 1 dentro del EXISTS porque el valor concreto no importa (solo importa si hay filas o no).
Clientes con pedidos
Encontrar clientes que han realizado al menos un pedido:
SELECT cl.nombre, cl.apellidos
FROM clientes cl
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = cl.id
)
ORDER BY cl.nombre;| nombre | apellidos |
|---|---|
| Alejandro | Serrano Blanco |
| Ana | Martínez Ruiz |
| Andrés | Vega Romero |
| Carlos | Rodríguez Martín |
| Carmen | Ruiz Jiménez |
| David | Sánchez Moreno |
| Elena | Romero Navarro |
| Fernando | Molina Ortega |
| Isabel | Gil Molina |
| Javier | Moreno Díaz |
| Laura | López Sánchez |
| Lucía | Díaz Hernández |
| María | García López |
| Marta | Blanco Castro |
| Miguel | Hernández Torres |
| Paula | Ortega Serrano |
| Pedro | Fernández Castro |
| Roberto | Navarro Gil |
| Sara | Torres Vega |
Para cada cliente, MySQL ejecuta la subconsulta buscando si existe al menos un pedido con ese cliente_id. En cuanto encuentra uno, devuelve TRUE y pasa al siguiente cliente. No necesita contar cuántos pedidos hay ni leer todos. Esto hace que EXISTS sea muy eficiente.
19 de los 20 clientes han hecho al menos un pedido. Diego es el único que falta.
NOT EXISTS
NOT EXISTS es la negación: devuelve TRUE cuando la subconsulta no encuentra ninguna fila. Es el patrón ideal para encontrar registros sin relación:
SELECT cl.nombre, cl.apellidos, cl.email
FROM clientes cl
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = cl.id
);| nombre | apellidos | |
|---|---|---|
| Diego | Castro Iglesias | diego.castro@email.com |
Diego es el único cliente sin pedidos. Este patrón es equivalente al LEFT JOIN ... WHERE IS NULL que vimos en la sección de JOINs, pero muchos desarrolladores consideran que NOT EXISTS expresa la intención de forma más clara.
Productos sin reseñas
SELECT p.nombre, p.precio
FROM productos p
WHERE NOT EXISTS (
SELECT 1
FROM resenas r
WHERE r.producto_id = p.id
)
ORDER BY p.precio DESC
LIMIT 5;| nombre | precio |
|---|---|
| Lenovo ThinkPad X1 | 1549.00 |
| Sofá 3 plazas | 599.00 |
| Estantería modular | 149.99 |
| Zapatillas trail | 149.99 |
| Vaqueros slim fit | 59.99 |
Los 5 productos más caros que nadie ha reseñado. El Lenovo ThinkPad lidera la lista con 1549 euros y ninguna opinión.
EXISTS vs IN
EXISTS y IN pueden resolver problemas similares. Clientes con pedidos entregados:
-- Con EXISTS
SELECT cl.nombre
FROM clientes cl
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = cl.id
AND p.estado = 'entregado'
)
ORDER BY cl.nombre;
-- Con IN (resultado equivalente)
SELECT cl.nombre
FROM clientes cl
WHERE cl.id IN (
SELECT cliente_id
FROM pedidos
WHERE estado = 'entregado'
)
ORDER BY cl.nombre;| nombre |
|---|
| Ana |
| Carlos |
| Isabel |
| Laura |
| María |
| Pedro |
Ambas devuelven los mismos 6 clientes. La diferencia está en cómo MySQL las ejecuta internamente:
EXISTS es generalmente más rápido cuando la tabla exterior es pequeña y la interior es grande, porque MySQL puede dejar de buscar al encontrar la primera coincidencia. IN puede ser más rápido cuando la subconsulta devuelve pocos valores, porque MySQL puede optimizar la lista de valores en memoria.
En la práctica, las versiones modernas de MySQL optimizan ambas formas de manera similar en la mayoría de casos.
NOT EXISTS vs NOT IN
Aquí sí hay una diferencia importante. NOT IN tiene un problema con NULLs:
-- NOT IN: si la subconsulta devuelve algún NULL, el resultado es vacío
SELECT nombre FROM clientes
WHERE id NOT IN (SELECT cliente_id FROM pedidos);
-- Funciona porque cliente_id no tiene NULLs
-- NOT EXISTS: funciona siempre, sin importar los NULLs
SELECT nombre FROM clientes cl
WHERE NOT EXISTS (
SELECT 1 FROM pedidos p WHERE p.cliente_id = cl.id
);Si la columna cliente_id pudiera contener NULLs, NOT IN devolvería un resultado vacío (como explicamos en el artículo de NOT IN). NOT EXISTS no tiene este problema porque no compara valores, solo comprueba si hay filas.
Por esta razón, NOT EXISTS es la opción más segura y recomendada para buscar "los que no tienen".
EXISTS con múltiples condiciones
Puedes añadir condiciones complejas dentro del EXISTS. Clientes que han hecho al menos un pedido de más de 500 euros que fue entregado:
SELECT cl.nombre, cl.apellidos
FROM clientes cl
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = cl.id
AND p.total > 500
AND p.estado = 'entregado'
)
ORDER BY cl.nombre;| nombre | apellidos |
|---|---|
| Carlos | Rodríguez Martín |
| María | García López |
| Pedro | Fernández Castro |
Solo 3 clientes cumplen todas las condiciones: haber hecho un pedido de más de 500 euros que además fue entregado.
EXISTS doble
Puedes combinar varios EXISTS para condiciones más elaboradas. Clientes que han comprado y han dejado reseñas:
SELECT cl.nombre, cl.apellidos
FROM clientes cl
WHERE EXISTS (
SELECT 1 FROM pedidos p WHERE p.cliente_id = cl.id
)
AND EXISTS (
SELECT 1 FROM resenas r WHERE r.cliente_id = cl.id
)
ORDER BY cl.nombre;Esto es equivalente al INTERSECT que vimos en la sección anterior, pero usando EXISTS. La ventaja de EXISTS es que funciona en todas las versiones de MySQL, mientras que INTERSECT requiere 8.0.31+.
Practica con EXISTS
Usa el editor para verificar la existencia de filas relacionadas:
En el siguiente artículo exploraremos las tablas derivadas en detalle, una técnica que usa subconsultas en el FROM para crear resultados intermedios.
Escrito por Eduardo Lázaro
