4. Consultas Avanzadas
Consultas Avanzadas
Section titled “Consultas Avanzadas”Las consultas avanzadas en SQL permiten realizar operaciones complejas sobre los datos, como cálculos agregados, combinación de tablas y subconsultas. Estas técnicas son fundamentales para el análisis de datos y la generación de informes en sistemas de bases de datos relacionales.
4.1. Funciones de agregación (SUM, AVG, COUNT, MAX, MIN)
Section titled “4.1. Funciones de agregación (SUM, AVG, COUNT, MAX, MIN)”Las funciones de agregación realizan cálculos sobre conjuntos de filas y devuelven un único valor. Son herramientas poderosas para el análisis de datos y estadísticas.
Funciones de agregación básicas
Section titled “Funciones de agregación básicas”La función COUNT cuenta el número de filas o valores no nulos en un conjunto de resultados.
-- Contar todas las filas de una tablaSELECT COUNT(*) AS total_empleados FROM empleados;
-- Contar valores no nulos en una columnaSELECT COUNT(telefono) AS empleados_con_telefono FROM empleados;
-- Contar valores únicosSELECT COUNT(DISTINCT departamento_id) AS num_departamentos FROM empleados;La función SUM calcula la suma de los valores en una columna numérica.
-- Sumar todos los salariosSELECT SUM(salario) AS suma_salarios FROM empleados;
-- Sumar con condiciónSELECT SUM(salario) AS suma_salarios_ventasFROM empleadosWHERE departamento_id = 3;
-- Sumar valores calculadosSELECT SUM(precio * cantidad) AS valor_total_inventarioFROM productos;La función AVG calcula el promedio (media aritmética) de los valores en una columna numérica.
-- Calcular el salario promedioSELECT AVG(salario) AS salario_promedio FROM empleados;
-- Promedio con condiciónSELECT AVG(salario) AS salario_promedio_gerentesFROM empleadosWHERE cargo LIKE '%Gerente%';
-- Promedio con DISTINCT (valores únicos)SELECT AVG(DISTINCT precio) AS precio_promedio_unicoFROM productos;Las funciones MAX y MIN devuelven el valor máximo y mínimo respectivamente de una columna.
-- Encontrar el salario máximo y mínimoSELECT MAX(salario) AS salario_maximo, MIN(salario) AS salario_minimoFROM empleados;
-- Encontrar la fecha más reciente y más antiguaSELECT MAX(fecha_pedido) AS pedido_mas_reciente, MIN(fecha_pedido) AS pedido_mas_antiguoFROM pedidos;
-- Combinar con otras columnas (MySQL)SELECT departamento_id, MAX(salario) AS salario_maximoFROM empleadosGROUP BY departamento_id;Funciones de agregación avanzadas
Section titled “Funciones de agregación avanzadas”-- STDDEV: Desviación estándarSELECT STDDEV(salario) AS desviacion_salario FROM empleados;
-- VARIANCE: VarianzaSELECT VARIANCE(salario) AS varianza_salario FROM empleados;
-- GROUP_CONCAT: Concatenar valores de grupoSELECT departamento_id, GROUP_CONCAT(nombre SEPARATOR ', ') AS empleadosFROM empleadosGROUP BY departamento_id;-- STDDEV: Desviación estándarSELECT STDDEV(salario) AS desviacion_salario FROM empleados;
-- VARIANCE: VarianzaSELECT VARIANCE(salario) AS varianza_salario FROM empleados;
-- LISTAGG: Concatenar valores de grupo (equivalente a GROUP_CONCAT)SELECT departamento_id, LISTAGG(nombre, ', ') WITHIN GROUP (ORDER BY nombre) AS empleadosFROM empleadosGROUP BY departamento_id;4.2. Agrupar datos (GROUP BY)
Section titled “4.2. Agrupar datos (GROUP BY)”La cláusula GROUP BY permite agrupar filas que tienen los mismos valores en columnas especificadas. Se utiliza comúnmente con funciones de agregación para realizar cálculos sobre cada grupo.
Agrupación básica
Section titled “Agrupación básica”-- Contar empleados por departamentoSELECT departamento_id, COUNT(*) AS num_empleadosFROM empleadosGROUP BY departamento_id;
-- Calcular salario promedio por departamentoSELECT departamento_id, AVG(salario) AS salario_promedioFROM empleadosGROUP BY departamento_id;Agrupación por múltiples columnas
Section titled “Agrupación por múltiples columnas”-- Agrupar por departamento y cargoSELECT departamento_id, cargo, COUNT(*) AS num_empleados, AVG(salario) AS salario_promedioFROM empleadosGROUP BY departamento_id, cargo;Agrupación con ordenación
Section titled “Agrupación con ordenación”-- Ordenar grupos por el número de empleados (descendente)SELECT departamento_id, COUNT(*) AS num_empleadosFROM empleadosGROUP BY departamento_idORDER BY num_empleados DESC;Agrupación con expresiones
Section titled “Agrupación con expresiones”-- Agrupar por año de contrataciónSELECT YEAR(fecha_contratacion) AS anio, COUNT(*) AS num_contratacionesFROM empleadosGROUP BY YEAR(fecha_contratacion)ORDER BY anio;-- Agrupar por rango de salarioSELECT CASE WHEN salario < 30000 THEN 'Bajo' WHEN salario BETWEEN 30000 AND 60000 THEN 'Medio' ELSE 'Alto' END AS rango_salario, COUNT(*) AS num_empleadosFROM empleadosGROUP BY rango_salario;-- Agrupar por rango de salarioSELECT CASE WHEN salario < 30000 THEN 'Bajo' WHEN salario BETWEEN 30000 AND 60000 THEN 'Medio' ELSE 'Alto' END AS rango_salario, COUNT(*) AS num_empleadosFROM empleadosGROUP BY CASE WHEN salario < 30000 THEN 'Bajo' WHEN salario BETWEEN 30000 AND 60000 THEN 'Medio' ELSE 'Alto'END;Agrupación con ROLLUP (Totales y subtotales)
Section titled “Agrupación con ROLLUP (Totales y subtotales)”-- Generar subtotales y total generalSELECT departamento_id, cargo, SUM(salario) AS suma_salariosFROM empleadosGROUP BY departamento_id, cargo WITH ROLLUP;-- Generar subtotales y total generalSELECT departamento_id, cargo, SUM(salario) AS suma_salariosFROM empleadosGROUP BY ROLLUP(departamento_id, cargo);4.3. Filtrar grupos (HAVING)
Section titled “4.3. Filtrar grupos (HAVING)”La cláusula HAVING permite filtrar grupos basados en condiciones de agregación. Mientras que WHERE filtra filas individuales antes de la agrupación, HAVING filtra los grupos después de que se han formado.
Sintaxis básica
Section titled “Sintaxis básica”SELECT columna1, columna2, funcion_agregacion(columna3)FROM tabla[WHERE condicion_filas]GROUP BY columna1, columna2HAVING condicion_grupos[ORDER BY columna];Ejemplos de HAVING
Section titled “Ejemplos de HAVING”-- Encontrar departamentos con más de 5 empleadosSELECT departamento_id, COUNT(*) AS num_empleadosFROM empleadosGROUP BY departamento_idHAVING COUNT(*) > 5;
-- Departamentos con salario promedio superior a 50000SELECT departamento_id, AVG(salario) AS salario_promedioFROM empleadosGROUP BY departamento_idHAVING AVG(salario) > 50000;
-- Productos con ventas totales superiores a 10000SELECT p.id_producto, p.nombre, SUM(dp.cantidad * dp.precio) AS ventas_totalesFROM productos pJOIN detalles_pedido dp ON p.id_producto = dp.id_productoGROUP BY p.id_producto, p.nombreHAVING SUM(dp.cantidad * dp.precio) > 10000ORDER BY ventas_totales DESC;Combinando WHERE y HAVING
Section titled “Combinando WHERE y HAVING”-- Departamentos con salario promedio alto, excluyendo gerentesSELECT departamento_id, AVG(salario) AS salario_promedioFROM empleadosWHERE cargo NOT LIKE '%Gerente%' -- Filtro a nivel de filaGROUP BY departamento_idHAVING AVG(salario) > 40000; -- Filtro a nivel de grupoHAVING con expresiones complejas
Section titled “HAVING con expresiones complejas”-- Encontrar categorías donde el precio máximo es al menos el doble del precio promedioSELECT categoria_id, AVG(precio) AS precio_promedio, MAX(precio) AS precio_maximoFROM productosGROUP BY categoria_idHAVING MAX(precio) >= 2 * AVG(precio);
-- Productos con ventas en al menos 3 meses diferentesSELECT p.id_producto, p.nombre, COUNT(DISTINCT MONTH(dp.fecha_venta)) AS num_meses_con_ventasFROM productos pJOIN detalles_pedido dp ON p.id_producto = dp.id_productoGROUP BY p.id_producto, p.nombreHAVING COUNT(DISTINCT MONTH(dp.fecha_venta)) >= 3;4.4. Subconsultas (Subqueries)
Section titled “4.4. Subconsultas (Subqueries)”Las subconsultas son consultas anidadas dentro de otra consulta. Permiten realizar operaciones complejas y utilizar los resultados de una consulta como parte de otra.
Tipos de subconsultas
Section titled “Tipos de subconsultas”- Subconsultas escalares: Devuelven un único valor.
- Subconsultas de fila: Devuelven una sola fila con múltiples columnas.
- Subconsultas de lista: Devuelven una columna con múltiples filas.
- Subconsultas de tabla: Devuelven múltiples filas y columnas.
Subconsultas en la cláusula WHERE
Section titled “Subconsultas en la cláusula WHERE”-- Empleados con salario superior al promedioSELECT nombre, apellido, salarioFROM empleadosWHERE salario > (SELECT AVG(salario) FROM empleados);
-- Productos que no tienen ventasSELECT id_producto, nombreFROM productosWHERE id_producto NOT IN ( SELECT DISTINCT id_producto FROM detalles_pedido);Subconsultas con operadores de comparación
Section titled “Subconsultas con operadores de comparación”-- Empleados con el salario más alto en cada departamentoSELECT e.nombre, e.apellido, e.departamento_id, e.salarioFROM empleados eWHERE e.salario = ( SELECT MAX(salario) FROM empleados WHERE departamento_id = e.departamento_id);Subconsultas con EXISTS y NOT EXISTS
Section titled “Subconsultas con EXISTS y NOT EXISTS”-- Clientes que han realizado al menos un pedidoSELECT id_cliente, nombreFROM clientes cWHERE EXISTS ( SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id_cliente);
-- Clientes que nunca han realizado un pedidoSELECT id_cliente, nombreFROM clientes cWHERE NOT EXISTS ( SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id_cliente);Subconsultas en la cláusula FROM
Section titled “Subconsultas en la cláusula FROM”-- Promedio de ventas mensuales por productoSELECT producto_id, AVG(total_ventas) AS promedio_ventas_mensualesFROM ( SELECT p.id_producto AS producto_id, YEAR(dp.fecha_venta) AS anio, MONTH(dp.fecha_venta) AS mes, SUM(dp.cantidad * dp.precio) AS total_ventas FROM productos p JOIN detalles_pedido dp ON p.id_producto = dp.id_producto GROUP BY p.id_producto, YEAR(dp.fecha_venta), MONTH(dp.fecha_venta)) AS ventas_mensualesGROUP BY producto_id;Subconsultas en la cláusula SELECT
Section titled “Subconsultas en la cláusula SELECT”-- Mostrar el salario y la diferencia con el promedio del departamentoSELECT e.nombre, e.departamento_id, e.salario, (SELECT AVG(salario) FROM empleados WHERE departamento_id = e.departamento_id) AS salario_promedio_dept, e.salario - (SELECT AVG(salario) FROM empleados WHERE departamento_id = e.departamento_id) AS diferenciaFROM empleados e;Subconsultas correlacionadas
Section titled “Subconsultas correlacionadas”Las subconsultas correlacionadas hacen referencia a columnas de la consulta externa.
-- Empleados que ganan más que el promedio de su departamentoSELECT e1.nombre, e1.apellido, e1.departamento_id, e1.salarioFROM empleados e1WHERE e1.salario > ( SELECT AVG(e2.salario) FROM empleados e2 WHERE e2.departamento_id = e1.departamento_id);4.5. Consultas combinadas (UNION, INTERSECT, MINUS/EXCEPT)
Section titled “4.5. Consultas combinadas (UNION, INTERSECT, MINUS/EXCEPT)”Las operaciones de conjunto permiten combinar resultados de múltiples consultas. Cada consulta debe tener el mismo número de columnas y tipos de datos compatibles.
UNION y UNION ALL
Section titled “UNION y UNION ALL”UNION combina los resultados de dos o más consultas y elimina duplicados. UNION ALL incluye todos los registros, incluyendo duplicados.
-- Combinar clientes y proveedores en una lista de contactosSELECT id_cliente AS id, nombre, email, 'Cliente' AS tipoFROM clientesUNIONSELECT id_proveedor, nombre_empresa, email_contacto, 'Proveedor'FROM proveedoresORDER BY nombre;
-- Unir todos los registros (incluyendo duplicados)SELECT producto_id, fechaFROM ventasUNION ALLSELECT producto_id, fechaFROM devoluciones;INTERSECT
Section titled “INTERSECT”INTERSECT devuelve solo las filas que aparecen en ambos conjuntos de resultados.
MySQL no soporta directamente INTERSECT, pero se puede simular con IN o JOIN:
-- Productos que se han vendido y también han sido devueltosSELECT DISTINCT v.producto_idFROM ventas vWHERE v.producto_id IN ( SELECT DISTINCT producto_id FROM devoluciones);
-- Alternativa usando JOINSELECT DISTINCT v.producto_idFROM ventas vJOIN devoluciones d ON v.producto_id = d.producto_id;-- Productos que se han vendido y también han sido devueltosSELECT producto_idFROM ventasINTERSECTSELECT producto_idFROM devoluciones;EXCEPT/MINUS
Section titled “EXCEPT/MINUS”EXCEPT (SQL estándar) o MINUS (Oracle) devuelve las filas del primer conjunto que no aparecen en el segundo.
MySQL no soporta directamente EXCEPT, pero se puede simular con NOT IN o LEFT JOIN:
-- Productos que se han vendido pero nunca han sido devueltosSELECT DISTINCT v.producto_idFROM ventas vWHERE v.producto_id NOT IN ( SELECT DISTINCT producto_id FROM devoluciones);
-- Alternativa usando LEFT JOINSELECT DISTINCT v.producto_idFROM ventas vLEFT JOIN devoluciones d ON v.producto_id = d.producto_idWHERE d.producto_id IS NULL;-- Productos que se han vendido pero nunca han sido devueltosSELECT producto_idFROM ventasMINUSSELECT producto_idFROM devoluciones;4.6. Joins (INNER, LEFT, RIGHT, FULL)
Section titled “4.6. Joins (INNER, LEFT, RIGHT, FULL)”Los joins permiten combinar filas de dos o más tablas basándose en una condición relacionada.
Tipos de JOIN
Section titled “Tipos de JOIN”Devuelve filas cuando hay al menos una coincidencia en ambas tablas.
-- Obtener pedidos con información del clienteSELECT p.id_pedido, p.fecha_pedido, c.nombre AS nombre_cliente, c.emailFROM pedidos pINNER JOIN clientes c ON p.cliente_id = c.id_cliente;Devuelve todas las filas de la tabla izquierda y las filas coincidentes de la tabla derecha.
-- Obtener todos los clientes y sus pedidos (si los tienen)SELECT c.id_cliente, c.nombre, p.id_pedido, p.fecha_pedidoFROM clientes cLEFT JOIN pedidos p ON c.id_cliente = p.cliente_id;
-- Encontrar clientes sin pedidosSELECT c.id_cliente, c.nombreFROM clientes cLEFT JOIN pedidos p ON c.id_cliente = p.cliente_idWHERE p.id_pedido IS NULL;Devuelve todas las filas de la tabla derecha y las filas coincidentes de la tabla izquierda.
-- Obtener todos los pedidos y sus clientes (incluso si el cliente ya no existe)SELECT p.id_pedido, p.fecha_pedido, c.id_cliente, c.nombreFROM pedidos pRIGHT JOIN clientes c ON p.cliente_id = c.id_cliente;Devuelve filas cuando hay una coincidencia en una de las tablas.
MySQL no soporta directamente FULL JOIN, pero se puede simular con UNION:
-- Simular FULL JOIN en MySQLSELECT c.id_cliente, c.nombre, p.id_pedido, p.fecha_pedidoFROM clientes cLEFT JOIN pedidos p ON c.id_cliente = p.cliente_idUNIONSELECT c.id_cliente, c.nombre, p.id_pedido, p.fecha_pedidoFROM clientes cRIGHT JOIN pedidos p ON c.id_cliente = p.cliente_idWHERE c.id_cliente IS NULL;-- FULL JOIN en OracleSELECT c.id_cliente, c.nombre, p.id_pedido, p.fecha_pedidoFROM clientes cFULL OUTER JOIN pedidos p ON c.id_cliente = p.cliente_id;JOIN con múltiples tablas
Section titled “JOIN con múltiples tablas”-- Obtener detalles de pedidos con información de cliente y productoSELECT c.nombre AS cliente, p.id_pedido, p.fecha_pedido, dp.cantidad, pr.nombre AS producto, dp.precio AS precio_unitario, (dp.cantidad * dp.precio) AS subtotalFROM clientes cJOIN pedidos p ON c.id_cliente = p.cliente_idJOIN detalles_pedido dp ON p.id_pedido = dp.id_pedidoJOIN productos pr ON dp.id_producto = pr.id_productoORDER BY p.id_pedido, pr.nombre;JOIN con condiciones adicionales
Section titled “JOIN con condiciones adicionales”-- Pedidos recientes con productos carosSELECT p.id_pedido, p.fecha_pedido, c.nombre AS cliente, pr.nombre AS producto, dp.precioFROM pedidos pJOIN clientes c ON p.cliente_id = c.id_clienteJOIN detalles_pedido dp ON p.id_pedido = dp.id_pedidoJOIN productos pr ON dp.id_producto = pr.id_productoWHERE p.fecha_pedido >= '2023-01-01' AND dp.precio > 100ORDER BY p.fecha_pedido DESC;JOIN con alias de tabla
Section titled “JOIN con alias de tabla”-- Comparar salarios de empleados con sus gerentesSELECT e.nombre AS empleado, e.salario AS salario_empleado, g.nombre AS gerente, g.salario AS salario_gerenteFROM empleados eJOIN empleados g ON e.id_gerente = g.id_empleadoWHERE e.salario > g.salario;4.7. Joins cruzados (CROSS JOIN)
Section titled “4.7. Joins cruzados (CROSS JOIN)”Un CROSS JOIN devuelve el producto cartesiano de las filas de las tablas, es decir, cada fila de la primera tabla combinada con cada fila de la segunda tabla.
Sintaxis básica
Section titled “Sintaxis básica”SELECT columna1, columna2, ...FROM tabla1CROSS JOIN tabla2;
-- Sintaxis alternativa (implícita)SELECT columna1, columna2, ...FROM tabla1, tabla2;Ejemplos de CROSS JOIN
Section titled “Ejemplos de CROSS JOIN”-- Generar todas las combinaciones posibles de productos y categoríasSELECT p.id_producto, p.nombre AS producto, c.id_categoria, c.nombre AS categoriaFROM productos pCROSS JOIN categorias c;
-- Generar una tabla de multiplicarSELECT a.n AS factor1, b.n AS factor2, a.n * b.n AS productoFROM (SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) aCROSS JOIN (SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) bORDER BY factor1, factor2;Casos de uso para CROSS JOIN
Section titled “Casos de uso para CROSS JOIN”- Generar combinaciones: Útil para crear todas las combinaciones posibles entre elementos.
-- Generar todas las combinaciones de tamaños y colores para productosSELECT p.id_producto, p.nombre AS producto, t.valor AS talla, c.valor AS colorFROM productos pCROSS JOIN (SELECT 'S' AS valor UNION SELECT 'M' UNION SELECT 'L' UNION SELECT 'XL') tCROSS JOIN (SELECT 'Rojo' AS valor UNION SELECT 'Azul' UNION SELECT 'Negro' UNION SELECT 'Blanco') c;- Rellenar fechas: Generar series de fechas o números.
-- Generar un calendario para un mes específicoSELECT DATE_ADD('2023-06-01', INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY) AS fechaFROM (SELECT 0 AS a UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) aCROSS JOIN (SELECT 0 AS a UNION SELECT 1 UNION SELECT 2) bCROSS JOIN (SELECT 0 AS a) cWHERE DATE_ADD('2023-06-01', INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY) <= '2023-06-30'ORDER BY fecha;Diferencias entre CROSS JOIN y otros JOINs
Section titled “Diferencias entre CROSS JOIN y otros JOINs”-- CROSS JOIN: No tiene condición de uniónSELECT e.nombre, d.nombre_departamentoFROM empleados eCROSS JOIN departamentos d;
-- INNER JOIN: Tiene condición de uniónSELECT e.nombre, d.nombre_departamentoFROM empleados eINNER JOIN departamentos d ON e.departamento_id = d.id_departamento;