Volver a SQL Básico
Subconsultas y SELECT Avanzado
Las subconsultas permiten usar el resultado de una consulta dentro de otra.
Subconsulta Escalar (valor único)
-- Clientes que gastaron más que el promedio
SELECT nombre, apellido
FROM clientes
WHERE id IN (
SELECT cliente_id
FROM pedidos
WHERE total > (SELECT AVG(total) FROM pedidos)
);
-- Productos más caros que el precio promedio
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos)
ORDER BY precio DESC;
Subconsulta en FROM (tabla derivada)
-- Ingresos mes a mes con crecimiento
SELECT
mes,
ingresos,
LAG(ingresos) OVER (ORDER BY mes) AS mes_anterior,
ingresos - LAG(ingresos) OVER (ORDER BY mes) AS crecimiento
FROM (
SELECT
DATE_FORMAT(creado_en, '%Y-%m') AS mes,
SUM(total) AS ingresos
FROM pedidos
GROUP BY DATE_FORMAT(creado_en, '%Y-%m')
) AS ingresos_mensuales
ORDER BY mes;
EXISTS / NOT EXISTS
-- Clientes que han hecho al menos un pedido
SELECT nombre, apellido
FROM clientes c
WHERE EXISTS (
SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id
);
-- Productos que nunca fueron pedidos
SELECT nombre, precio
FROM productos p
WHERE NOT EXISTS (
SELECT 1 FROM items_pedido ip WHERE ip.producto_id = p.id
);
Expresiones de Tabla Comunes (CTE)
Los CTEs hacen las consultas complejas más legibles:
-- Mejores clientes con estadísticas de pedidos
WITH estadisticas_cliente AS (
SELECT
cliente_id,
COUNT(*) AS cantidad_pedidos,
SUM(total) AS valor_vida
FROM pedidos
GROUP BY cliente_id
),
niveles_cliente AS (
SELECT
c.id,
c.nombre,
c.apellido,
ec.valor_vida,
CASE
WHEN ec.valor_vida >= 5000 THEN 'Oro'
WHEN ec.valor_vida >= 1000 THEN 'Plata'
ELSE 'Bronce'
END AS nivel
FROM clientes c
JOIN estadisticas_cliente ec ON c.id = ec.cliente_id
)
SELECT * FROM niveles_cliente WHERE nivel = 'Oro' ORDER BY valor_vida DESC;
CTE Recursivo
-- Recorrer jerarquía organizacional
WITH RECURSIVE organigrama AS (
-- Caso base: directivos de nivel superior
SELECT id, nombre, gerente_id, 0 AS nivel, nombre AS ruta
FROM empleados
WHERE gerente_id IS NULL
UNION ALL
-- Caso recursivo: empleados con un gerente
SELECT e.id, e.nombre, e.gerente_id, o.nivel + 1,
CONCAT(o.ruta, ' > ', e.nombre)
FROM empleados e
JOIN organigrama o ON e.gerente_id = o.id
)
SELECT nivel, ruta FROM organigrama ORDER BY ruta;
Funciones de Ventana
Las funciones de ventana calculan un valor sobre un conjunto de filas relacionadas con la fila actual:
-- Número de fila por cliente (último pedido primero)
SELECT
id,
cliente_id,
total,
ROW_NUMBER() OVER (PARTITION BY cliente_id ORDER BY creado_en DESC) AS num_fila
FROM pedidos;
-- Total acumulado de ingresos
SELECT
DATE(creado_en) AS dia,
SUM(total) AS ingreso_diario,
SUM(SUM(total)) OVER (ORDER BY DATE(creado_en)) AS total_acumulado
FROM pedidos
GROUP BY DATE(creado_en);
-- Clasificar productos por precio dentro de cada categoría
SELECT
nombre,
categoria,
precio,
RANK() OVER (PARTITION BY categoria ORDER BY precio DESC) AS rango_precio
FROM productos;
Expresión CASE
-- Lógica condicional en consultas
SELECT
nombre,
precio,
CASE
WHEN precio < 50 THEN 'Económico'
WHEN precio < 200 THEN 'Rango Medio'
WHEN precio < 500 THEN 'Premium'
ELSE 'Lujo'
END AS nivel_precio,
CASE WHEN stock = 0 THEN 'Sin Stock' ELSE 'En Stock' END AS disponibilidad
FROM productos;
Funciones de Cadena
SELECT
CONCAT(nombre, ' ', apellido) AS nombre_completo,
UPPER(email) AS email_mayusculas,
LENGTH(telefono) AS longitud_telefono,
TRIM(' hola ') AS recortado,
REPLACE(telefono, '-', '') AS telefono_limpio
FROM clientes;
Funciones de Fecha
SELECT
creado_en,
DATE(creado_en) AS fecha_pedido,
DATE_FORMAT(creado_en, '%Y-%m') AS año_mes,
DATEDIFF(NOW(), creado_en) AS dias_atras,
DATE_ADD(creado_en, INTERVAL 30 DAY) AS fecha_vencimiento,
YEAR(creado_en) AS año,
MONTH(creado_en) AS mes,
DAYOFWEEK(creado_en) AS dia_semana
FROM pedidos;