Aprende SQL · lección gratuita

Lección 23 · Subconsultas con IN, ANY y ALL

Resumen

Cuando una subconsulta devuelve varias filas ya no puedes compararla con = o > directamente: necesitas un operador que trabaje sobre un conjunto de valores. Los tres clásicos del estándar SQL son IN (¿el valor está en la lista?), ANY/SOME (¿cumple la comparación con al menos uno?) y ALL (¿la cumple con todos?). SQLite implementa IN y NOT IN de forma nativa, pero no soporta la sintaxis op ANY/ALL (subconsulta); veremos el equivalente exacto y portable con agregados.

Sintaxis / Conceptos

IN y NOT IN

columna IN (SELECT ...) compara la columna contra el conjunto completo de valores. Es la forma más legible de expresar "pertenece a este grupo". NOT IN es su negación y se usa muchísimo para encontrar filas sin correspondencia (anti-join). El motor evalúa la subconsulta una vez y mantiene el conjunto de valores.

ANY / ALL y su equivalente en SQLite

En motores como PostgreSQL o MySQL puedes escribir precio > ALL (SELECT precio FROM productos WHERE categoria_id = 1). Conceptualmente:

⚠️ No asumas que ANY siempre se traduce a MIN ni ALL a MAX: depende del operador. La regla general es que ANY usa el extremo que más fácilmente cumple la comparación (el más permisivo) y ALL el más exigente. Por eso con > se invierte respecto a <: para > ANY basta superar el mínimo, pero para < ANY basta quedar por debajo del máximo.

SQLite no acepta > ANY (...) ni > ALL (...): lanza un error de sintaxis. La traducción correcta y portable es usar el agregado equivalente:

EstándarEquivalente en SQLite
x > ALL(SELECT c FROM t)x > (SELECT MAX(c) FROM t)
x < ALL(SELECT c FROM t)x < (SELECT MIN(c) FROM t)
x > ANY(SELECT c FROM t)x > (SELECT MIN(c) FROM t)
x < ANY(SELECT c FROM t)x < (SELECT MAX(c) FROM t)
x = ANY(SELECT c FROM t)x IN (SELECT c FROM t)
x <> ALL(SELECT c FROM t)x NOT IN (SELECT c FROM t)
SELECT empresa, (SELECT COUNT(*) FROM pedidos WHERE cliente_id = c.id) AS total_pedidos FROM clientes c ORDER BY total_pedidos DESC LIMIT 5

Ejemplos

-- IN: clientes que han hecho ALGÚN pedido cancelado (semi-join por pertenencia).
SELECT empresa, pais
FROM clientes
WHERE id IN (SELECT cliente_id FROM pedidos WHERE estado = 'Cancelado')
ORDER BY empresa;

-- NOT IN: productos NUNCA vendidos (no aparecen en ninguna línea de detalle). Anti-join clásico.
SELECT id, nombre
FROM productos
WHERE id NOT IN (SELECT producto_id FROM detalle_pedidos)
ORDER BY id;

-- ANY (estándar: precio > ANY de la categoría 3) => en SQLite: > MIN de esa categoría.
-- "Productos más caros que AL MENOS UNO de los servidores (categoría 3)".
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT MIN(precio) FROM productos WHERE categoria_id = 3)
ORDER BY precio
LIMIT 5;

-- ALL (estándar: precio > ALL del soporte) => en SQLite: > MAX de la categoría 5.
-- "Productos más caros que TODOS los de Soporte y Mantenimiento".
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT MAX(precio) FROM productos WHERE categoria_id = 5)
ORDER BY precio;
💡 Si intentas WHERE precio > ALL (SELECT ...) en SQLite obtendrás near "ALL": syntax error. Convierte mentalmente > ALL> (SELECT MAX ...) y > ANY> (SELECT MIN ...). Para igualdad usa directamente IN / NOT IN.

Cheatsheet

OperadorQué haceEn SQLite
IN (subq)¿pertenece al conjunto?✅ nativo
NOT IN (subq)¿no pertenece? (anti-join)✅ nativo (ojo con NULLs)
> ALL (subq)mayor que todosusar > (SELECT MAX ...)
> ANY (subq)mayor que algunousar > (SELECT MIN ...)
= ANY (subq)igual a algunoequivale a IN (subq)

---

← Subconsultas escalaresSubconsultas correlacionadas y EXISTS →

Ver todas las lecciones de Aprende SQL →