SQL – Consultas complejas
En este capítulo vamos a aprender a crear sentencias SELECT más complejas. Vamos a aprender funciones que proporcionan al lenguaje para calcular o recuperar datos más elaborados y a agregar y crear varias vistas de consulta.
Funciones
Funciones de agregación
Las funciones de agregación se utilizan para realizar cálculos sobre un conjunto de filas (grupo de datos) y devolver un solo valor de resultado.
Estas funciones suelen utilizarse junto con la cláusula GROUP BY, que veremos a continuación.
1. COUNT()
Cuenta el número de filas (o valores no nulos) en un conjunto.
Ejemplo:
SELECT COUNT(*) AS Total_Empleados FROM Empleados;
Devuelve el número total de empleados.
Total_Empleados
------------------------
10
2. SUM()
Suma los valores de una columna numérica.
Ejemplo:
SELECT SUM(Salario_Empleado) AS Total_Sueldos FROM Empleados;
Devuelve la suma total de los sueldos de todos los empleados.
Total_Sueldos
-------------------
329200
3. AVG()
Calcula el promedio (media aritmética) de una columna numérica.
Ejemplo:
SELECT AVG(Salario_Empleado) AS Sueldo_Promedio FROM Empleados;
Devuelve el salario promedio.
Sueldo_Promedio
-----------------------
332800
4. MAX()
Devuelve el valor máximo de una columna.
Ejemplo:
SELECT MAX(Salario_Empleado) AS Sueldo_Máximo FROM Empleados;
Devuelve el salario más alto.
Sueldo_Máximo
----------------------
44929
5. MIN()
Devuelve el valor mínimo de una columna.
Ejemplo:
SELECT MIN(Salario) AS Sueldo_Mínimo FROM Empleados;
Devuelve el salario más bajo.
Sueldo_Mínimo
---------------------
19889
Otras funciones
1. Funciones matemáticas
| Función | Descripción | Ejemplo |
|---|---|---|
| ABS(x) | Valor absoluto | SELECT ABS(-10); → 10 |
| ROUND(x, n) | Redondea un número a n decimales | SELECT ROUND(10.567, 2); → 10.57 |
| CEIL(x) / CEILING(x) | Redondea hacia arriba al entero más cercano | SELECT CEIL(10.2); → 11 |
| FLOOR(x) | Redondea hacia abajo | SELECT FLOOR(10.9); → 10 |
| MOD(x, y) | Resto de la división | SELECT MOD(10, 3); → 1 |
| POWER(x, y) | Potencia | SELECT POWER(2, 3); → 8 |
| SQRT(x) | Raíz cuadrada | SELECT SQRT(25); → 5 |
2. Funciones de fecha y hora
| Función | Descripción | Ejemplo |
|---|---|---|
| NOW() | Fecha y hora actual | SELECT NOW(); |
| CURRENT_DATE | Solo la fecha actual | SELECT CURRENT_DATE; |
| YEAR(fecha) | Año de una fecha | SELECT YEAR(FechaNacimiento); |
| MONTH(fecha) | Mes de una fecha | SELECT MONTH(FechaNacimiento); |
| DAY(fecha) | Día del mes | SELECT DAY(FechaNacimiento); |
| DATEDIFF(f1, f2) | Diferencia entre fechas (varía por motor) | SELECT DATEDIFF('2025-12-31', '2025-01-01'); → 364 |
NOTA: Algunas funciones cambian un poco entre MySQL, SQL Server, PostgreSQL o Access. En Access, por ejemplo, usarías DateDiff("d", Fecha1, Fecha2).
3. Funciones de texto (cadenas)
| Función | Descripción | Ejemplo |
|---|---|---|
| UPPER(texto) | Convierte a mayúsculas | SELECT UPPER('hola'); → 'HOLA' |
| LOWER(texto) | Convierte a minúsculas | SELECT LOWER('HOLA'); → 'hola' |
| LENGTH(texto) | Longitud del texto (varía: LEN() en otros) | SELECT LENGTH('Hola'); → 4 |
| SUBSTRING(texto, inicio, longitud) | Extrae parte de un texto | SELECT SUBSTRING('Hola Mundo', 1, 4); → 'Hola' |
| CONCAT(t1, t2) | Une textos | SELECT CONCAT('Hola', ' Mundo'); → 'Hola Mundo' |
| TRIM(texto) | Elimina espacios al inicio y final | SELECT TRIM(' hola '); → 'hola' |
| REPLACE(texto, a, b) | Reemplaza parte del texto | SELECT REPLACE('Hola mundo', 'mundo', 'clase'); → 'Hola clase' |
4. Funciones de conversión (cast)
| Función | Descripción | Ejemplo |
|---|---|---|
| CAST(x AS tipo) | Convierte valores entre tipos | CAST('2025-01-01' AS DATE) |
| CONVERT(tipo, x) | Similar a CAST (más común en SQL Server) | CONVERT(DATE, '2025-01-01') |
NOTA: Aprender a combinar funciones (por ejemplo: UPPER(SUBSTRING(...))) te permitirá transformar y analizar datos de forma mucho más potente y flexible.
¿Qué hace LIKE?
La cláusula LIKE se usa en SQL para buscar patrones en cadenas de texto.
LIKE se utiliza principalmente en combinación con los comodines:
- * →Cualquier número de caracteres (equivalente a % en otros motores).
- ? → Un solo carácter (equivalente a _ en otros motores).
- [ ] → Cualquier carácter dentro de un conjunto.
- ! dentro de [ ] → Excluye los caracteres.
Sintaxis básica
SELECT * FROM NombreTabla WHERE CampoTexto LIKE "patrón";
Ejemplos prácticos en Access
| Consulta SQL | Descripción |
|---|---|
| LIKE "A*" | Comienza con "A" |
| LIKE "*n" | Termina en "n" |
| LIKE "ab" | Contiene "ab" |
| LIKE "???123" | Exactamente 3 caracteres + "123" |
| LIKE "[AB]*" | Empieza con A o B |
| LIKE "[!AB]*" | No empieza con A ni B |
| LIKE "[0-9]" | Contiene al menos un número |
Ejemplo real
SELECT Nombre_Empleado FROM Empleados WHERE Nombre_Empleado LIKE "Mar*";
Devuelve empleados cuyos nombres empiezan por "Mar", como "María", "Marco", "Martin".
NOTA: El carácter comodín cambia de unos bases de datos a otras, por ejemplo:
| Comodín | Access | Otros SQL |
|---|---|---|
| Cualquier texto | * | % |
| Un solo carácter | ? | _ |
¿Qué es GROUP BY?
La cláusula GROUP BY se utiliza en SQL para agrupar filas que tienen valores iguales en columnas específicas, y a menudo se utiliza junto con funciones de agregación como SUM, COUNT, AVG, MAX o MIN.
Sintaxis básica:
SELECT columna1, función_agregada(columna2) FROM tabla GROUP BY columna1;
Ejemplo 1: Contar empleados por departamento
➢
SELECT Departamento_id, COUNT(*) AS Total_Empleados FROM Empleados GROUP BY Departamento_id;
Resultado:
| Departamento_id | Total_Empleados |
|---|---|
| Teleco | 3 |
| IA | 2 |
| Matemáticas | 1 |
Ejemplo 2: Salario promedio por departamento
➢
SELECT Departamento_id, AVG(Salario_Empleado) AS Salario_Promedio FROM Empleados GROUP BY Departamento_id;
Resultado:
| Departamento_id | Salario_Promedio |
|---|---|
| IA | 29350 |
| Matemáticas | 34400 |
Notas importantes:
- Todas las columnas en el SELECT deben estar en el GROUP BY o ser agregadas.
Esto provocará error:
SELECT Nombre_Empleado, COUNT(*) FROM Empleados GROUP BY Departamento_id;
- Puedes agrupar por más de una columna:
SELECT Departamento_id, FechaNacimiento_Empleado, COUNT(*) FROM Empleados GROUP BY Departamento_id, FechaNacimiento_Empleado;
¿Qué es HAVING?
La cláusula HAVING se usa en SQL para filtrar los resultados después de agruparlos con GROUP BY.
A diferencia de WHERE, que filtra antes de agrupar, HAVING se aplica después de hacer los agrupamientos.
Sintaxis básica
SELECT columna1, función_agregada(columna2) FROM tabla GROUP BY columna1 HAVING condición_sobre_función;
Ejemplo 1: Filtrar grupos con COUNT()
Objetivo: Mostrar solo los departamentos que tienen más de 3 empleados.
SELECT Departamento_id, COUNT(*) AS TotalEmpleados FROM Empleados GROUP BY Departamento_id HAVING COUNT(*) > 3;
Ejemplo 2: Filtrar por promedio con AVG()
Objetivo: Mostrar departamentos con sueldo promedio mayor a 1500.
SELECT Departamento_id, AVG(Salario_Empleado) AS Promedio FROM Empleados GROUP BY Departamento_id HAVING AVG(Salario_Empleado) > 1500;
Ejemplo 3: Usar WHERE + GROUP BY + HAVING
Objetivo: Calcular el total de sueldos de empleados con id de empleado mayor de 110, pero mostrar solo los departamentos cuyo total supera 5000.
SELECT Departamento_id, SUM(Salario_ Empleado) AS TotalSueldos FROM Empleados WHERE id_Empleado > 110 GROUP BY Departamento_id HAVING SUM(Empleado) > 5000;
Diferencias clave entre WHERE y HAVING
| Característica | WHERE | HAVING |
|---|---|---|
| Se aplica antes del GROUP BY | Sí | No |
| Se aplica después del GROUP BY | No | Sí |
| Se usa con columnas normales | Sí | Sí (solo si están en GROUP BY) |
| Se usa con funciones de agregación | No | Sí |
NOTAS:
- No uses funciones agregadas en WHERE: eso causará error.
- Siempre que necesites filtrar por COUNT, AVG, SUM, etc., usa HAVING.
- Puedes combinar múltiples condiciones en HAVING con AND / OR.
Se puede usar el HAVING sin GROUP BY si hay una agrupación implícita, como en este ejemplo:
Ejemplo válido sin GROUP BY:
SELECT COUNT(*) AS total_ventas FROM ventas HAVING COUNT(*) > 1000;
→ Esto te da el total de ventas en toda la tabla, y luego HAVING evalúa si ese total es mayor a 1000.
¿Por qué funciona?
Porque incluso sin GROUP BY, la consulta ya está generando un único grupo implícito: toda la tabla.
Pero esto no es válido:
SELECT nombre FROM empleados HAVING nombre = ‘Juan’; -- ERROR
Porque no estás usando una función agregada ni GROUP BY. Para eso se usa WHERE.
En resumen:
| Caso | ¿HAVING permitido? |
|---|---|
| Con GROUP BY y agregados | Sí |
| Sin GROUP BY, pero con agregados globales | Sí |
| Sin GROUP BY ni funciones agregadas | No |
SUBCONSULTAS
¿Qué es una subconsulta?
Una subconsulta (o consulta anidada) es una consulta dentro de otra consulta. Sirve para obtener datos que luego se usan en la consulta principal.
Tipos de subconsultas
| Tipo | Dónde se usa | Retorna |
|---|---|---|
| Escalar (scalar) | En el SELECT, WHERE o HAVING | Un solo valor |
| De una columna o fila | En IN, =, >, etc. | Uno o más valores |
| De múltiples filas y columnas | En FROM o con alias | Tabla temporal |
Ejemplo 1: Subconsulta escalar en WHERE
Mostrar empleados que ganan más que el salario promedio de la empresa.
SELECT Nombre_Empleado, Salario_Empleado FROM Empleados WHERE Salario_Empleado > ( SELECT AVG(Salario_Empleado) FROM Empleados );
Aquí se usa una subconsulta dentro de la clausula WHERE, en dónde se recupera de la tabla empleados la media de salarios de los empleados
Ejemplo 2: Subconsulta en SELECT
Mostrar cada empleado y cuánto gana por encima del salario mínimo.
SELECT Nombre_Empleado, Salario_Empleado, Salario_Empleado - (SELECT MIN(Salario_Empleado) FROM Empleados) AS Diferencia_Minima FROM Empleados;
Aquí se calcula el valor de un campo dentro de la lista de valores que muestra el SELECT
Ejemplo 3: Subconsulta en FROM
Calcular el salario promedio de empleados por departamento, y mostrar solo aquellos con un promedio mayor a 1500.
SELECT Departamento_id, Promedio FROM ( SELECT Departamento_id, AVG(Salario_Empleado) AS Promedio FROM Empleados GROUP BY Departamento_id ) AS PromediosPorDepto WHERE Promedio > 1500;
Aquí se crear virtualmente una tabla “PromediosPorDepto “ con una subconsulta, por lo tanto, se tiene que hacer en la parte del FROM. Los valores de esta tabla virtual se filtran con el WHERE y se imprimen con los valores del SELECT
Ejemplo 4: Subconsulta con IN
Mostrar los nombres de empleados que trabajan en departamentos que pertenecen a la UPM (usando otra tabla).
SELECT Nombre_Empleado FROM Empleados WHERE Departamento_id IN ( SELECT id_Departamento FROM Departamentos WHERE Ubicacion_Departamento like '*UPM*' );
Ejemplo 5: Subconsulta correlacionada (más avanzada)
Mostrar empleados cuyo salario es mayor que el salario promedio de su propio departamento.
SELECT E.Nombre_Empleado, E.Departamento_id, E.Salario_Empleado FROM Empleados E WHERE Salario_Empleado > ( SELECT AVG(Salario_Empleado) FROM Empleados WHERE Departamento_id = E.Departamento_id );
NOTA: Empleados E la E significa que uso un alias para la tabla Empleados, por lo que en lugar de por Empleado puedo poner únicamente E
Esta subconsulta se recalcula para cada fila, por eso es "correlacionada"
Sí
No
Sí