Subqueries en SQL Server: qué son y para qué sirven
Una subquery o subconsulta es una consulta SQL dentro de otra consulta. La consulta interna obtiene un dato —o una lista de datos— y la consulta externa usa ese resultado para filtrar, comparar o devolver información completa.
En este capítulo trabajamos con ventas facturadas y ventas delivery para resolver tres situaciones reales:
- Ver las ventas cuyo importe supera el promedio general.
- Obtener únicamente las ventas que fueron delivery usando
IN. - Encontrar la fila completa de la venta con mayor importe usando
MAX.
La ventaja principal es que las consultas se mantienen dinámicas: no dependés de copiar a mano un valor que puede cambiar cuando cambian los datos.
Tablas del ejemplo: movimientos facturados y delivery
La tabla MOVIMIENTOS_FACTURADOS concentra todas las ventas. MOVIMIENTOS_DELIVERY guarda los datos adicionales de aquellas ventas que fueron enviadas a domicilio.
CREATE TABLE MOVIMIENTOS_FACTURADOS (
ID_MOVIMIENTO INT PRIMARY KEY,
FECHA DATETIME NOT NULL,
CLIENTE VARCHAR(100) NOT NULL,
IMPORTE DECIMAL(10, 2) NOT NULL,
MEDIO_PAGO VARCHAR(30) NOT NULL
);
CREATE TABLE MOVIMIENTOS_DELIVERY (
ID_DELIVERY INT IDENTITY(1, 1) PRIMARY KEY,
ID_MOVIMIENTO INT NOT NULL,
DIRECCION VARCHAR(150) NOT NULL,
REPARTIDOR VARCHAR(100),
COSTO_ENVIO DECIMAL(10, 2) NOT NULL,
ESTADO VARCHAR(30) NOT NULL,
CONSTRAINT FK_MOVIMIENTOS_DELIVERY_FACTURADOS
FOREIGN KEY (ID_MOVIMIENTO)
REFERENCES MOVIMIENTOS_FACTURADOS(ID_MOVIMIENTO)
);
La columna que vincula ambas tablas es ID_MOVIMIENTO. En MOVIMIENTOS_DELIVERY funciona como foreign key: cada delivery debe corresponder a una venta facturada existente. Si querés profundizar en esa relación, revisá Primary Key y Foreign Key desde cero.
Qué es una subconsulta en SQL
Pensala como una consulta padre que contiene una consulta más pequeña. La interna se ejecuta para entregar el resultado que necesita la consulta externa.
SELECT *
FROM MOVIMIENTOS_FACTURADOS
WHERE IMPORTE > (
SELECT AVG(IMPORTE)
FROM MOVIMIENTOS_FACTURADOS
);
La subconsulta es este bloque:
SELECT AVG(IMPORTE)
FROM MOVIMIENTOS_FACTURADOS
Devuelve un único importe promedio. La consulta exterior toma ese resultado y devuelve cada venta cuyo IMPORTE sea superior.
Ejemplo 1: ventas que superan el importe promedio
Primero podés consultar el promedio de manera aislada para comprobar qué valor se está calculando:
SELECT AVG(IMPORTE) AS PromedioImporte
FROM MOVIMIENTOS_FACTURADOS;
Una opción sería copiar el número devuelto y escribirlo en un filtro:
SELECT *
FROM MOVIMIENTOS_FACTURADOS
WHERE IMPORTE > 1200.00;
Pero ese valor queda hardcodeado. Si actualizás un importe, agregás ventas o eliminás registros, el promedio real cambia y la consulta deja de representar la regla de negocio.
La versión correcta y dinámica usa una subquery:
SELECT *
FROM MOVIMIENTOS_FACTURADOS
WHERE IMPORTE > (
SELECT AVG(IMPORTE)
FROM MOVIMIENTOS_FACTURADOS
);
Cada vez que la ejecutás, SQL Server vuelve a calcular el promedio con los datos actuales. Por eso no tenés que editar el filtro manualmente.
Para repasar qué hacen AVG, MAX, MIN, SUM y COUNT, seguí con funciones de agregación en SQL Server.
Ejemplo 2: subquery con IN para obtener ventas delivery
Ahora la pregunta es distinta: queremos ver todas las ventas facturadas que también tienen un registro en MOVIMIENTOS_DELIVERY.
Primero, la consulta interna devuelve los IDs de movimientos que fueron delivery:
SELECT ID_MOVIMIENTO
FROM MOVIMIENTOS_DELIVERY;
Después usamos esa lista con IN en la tabla principal:
SELECT *
FROM MOVIMIENTOS_FACTURADOS
WHERE ID_MOVIMIENTO IN (
SELECT ID_MOVIMIENTO
FROM MOVIMIENTOS_DELIVERY
);
IN significa “está dentro de”. SQL Server toma los IDs devueltos por la subconsulta y conserva únicamente las filas de MOVIMIENTOS_FACTURADOS cuyos IDs estén en esa lista.
El resultado contiene la información de facturación de cada venta delivery: cliente, fecha, importe y medio de pago.
Contar cuántas ventas fueron delivery
Si en vez del detalle necesitás un total, reemplazá * por COUNT(*):
SELECT COUNT(*) AS CantidadVentasDelivery
FROM MOVIMIENTOS_FACTURADOS
WHERE ID_MOVIMIENTO IN (
SELECT ID_MOVIMIENTO
FROM MOVIMIENTOS_DELIVERY
);
Ejemplo 3: obtener la venta con mayor importe usando MAX
Esta consulta devuelve solamente el importe máximo:
SELECT MAX(IMPORTE) AS ImporteMaximo
FROM MOVIMIENTOS_FACTURADOS;
Es útil, pero no indica a qué cliente corresponde la venta ni muestra su fecha, medio de pago o ID. Para obtener la fila completa, comparamos todos los importes contra el valor máximo calculado por una subconsulta:
SELECT *
FROM MOVIMIENTOS_FACTURADOS
WHERE IMPORTE = (
SELECT MAX(IMPORTE)
FROM MOVIMIENTOS_FACTURADOS
);
La subconsulta obtiene el mayor IMPORTE; la consulta principal devuelve cada registro que coincide con él.
Resumen: cuándo usar cada subquery
| Necesidad | Subconsulta | Resultado |
|---|---|---|
| Ventas por encima de la media | AVG(IMPORTE) | Filtra usando un valor calculado dinámicamente. |
| Ventas que fueron delivery | ID_MOVIMIENTO IN (...) | Filtra usando una lista de IDs de otra tabla. |
| Datos de la venta más alta | IMPORTE = (SELECT MAX(...)) | Devuelve la fila completa, no solo el máximo. |
Las subqueries son especialmente útiles cuando una consulta necesita el resultado de otra para tomar una decisión. Empezá por estos tres patrones: una subconsulta que devuelve un valor (AVG o MAX) y una que devuelve varios valores para usar con IN.
Próximos pasos para practicar
- Cambiá un importe y ejecutá otra vez la consulta con
AVGpara comprobar que el filtro se actualiza solo. - Probá
MIN(IMPORTE)con la misma estructura para obtener la venta de menor importe. - Agregá un nuevo delivery y verificá cómo cambia el resultado de la consulta con
IN. - Ejecutá un
JOINentre las dos tablas para mostrar también dirección, repartidor y estado del envío.
Mirá la explicación paso a paso en el video completo de subqueries en SQL Server.