SELECT, Filtros, Agregaciones y JOINs · Semana 8 · Unidad II
Universidad San Sebastián
2026-05-01
¿Dónde Estamos?
W04–W05: Diseñamos el modelo MER de TechStyle, normalizamos hasta 3FN y generamos el script DDL.
W06: Ejecutamos el DDL — 9 tablas creadas — y cargamos los primeros datos.
W07: Completamos la carga de datos, implementamos transacciones ACID y verificamos la integridad.
Hoy (W08): La base de datos está completa y confiable. Ahora viene la parte que justifica todo el esfuerzo anterior: responder preguntas de negocio con SQL.
El objetivo de hoy: transformar filas y columnas en información útil para Roberto, María y Carlos.
El Problema de Roberto Esta Semana
Con TechStyle operativo, Roberto cita a Juan para una reunión urgente:
Roberto (CEO): “Necesito saber cuáles son nuestros productos más vendidos para decidir qué poner en la campaña de invierno.”
María (Marketing): “Yo necesito identificar qué clientes llevan más de 3 meses sin comprar. Son nuestro público objetivo para la campaña.”
Carlos (Operaciones): “¿Cuántos pedidos están pendientes de despacho esta semana? ¿Cuánto representa en pesos?”
Los tres están pidiendo lo mismo: que los datos en la base de datos se conviertan en respuestas concretas.
Juan sabe que ninguna de estas preguntas puede responderse mirando una tabla directamente — requieren consultas SQL.
María necesita sus clientes objetivos para la campaña de invierno:
-- Clientes de la Región Metropolitana en segmento PremiumSELECT nombre, apellido, email, segmentoFROM clientesWHERE region ='RM'AND segmento ='Premium'ORDERBY apellido ASC;
-- Productos de la categoría "Zapatillas" con precio entre $40.000 y $100.000SELECT nombre_producto, precio_venta, stockFROM productosWHERE categoria_id IN (SELECT categoria_id FROM categoriasWHERE nombre_categoria ='Zapatillas' )AND precio_venta BETWEEN40000AND100000;
LIMIT: cuando solo se necesitan los primeros N resultados — útil para “top 5”, “últimos 10”:
SELECT nombre_producto, precio_ventaFROM productosORDERBY precio_venta DESCLIMIT5; -- solo los 5 productos más caros
🚀 Desafío Rápido 1
Escribe la consulta SQL que responde cada pregunta. Solo tienes acceso a las tablas clientes, productos y ventas.
Pregunta A: ¿Qué productos tienen stock igual a cero? Muestra nombre y categoría.
Pregunta B: ¿Qué clientes registraron ventas en enero de 2026? Muestra solo cliente_id y fecha. (Pista: usa la tabla ventas y BETWEEN)
Pregunta C: ¿Cuáles son los 3 productos con mayor precio de venta? Muestra nombre y precio.
Pregunta D — Desafío: ¿Qué clientes no están en la RM ni en Valparaíso? ¿Cuántos son? (Pista: NOT IN o <> con AND)
Funciones de Agregación
Roberto no solo quiere ver filas — quiere resumir: totales, promedios, conteos.
-- ¿Cuántos clientes tiene TechStyle?SELECTCOUNT(*) AS total_clientes FROM clientes;-- ¿Cuál es el precio promedio de los productos?SELECTAVG(precio_venta) AS precio_promedio FROM productos;-- ¿Cuánto vale el inventario total?SELECTSUM(precio_venta * stock) AS valor_inventario FROM productos;-- ¿Cuál es el producto más caro y el más barato?SELECTMAX(precio_venta) AS precio_maximo,MIN(precio_venta) AS precio_minimoFROM productos;
Funciones de Agregación
Función
Qué calcula
COUNT(*)
Número de filas
COUNT(columna)
Filas donde esa columna no es NULL
SUM(columna)
Suma de todos los valores
AVG(columna)
Promedio
MAX(columna)
Valor máximo
MIN(columna)
Valor mínimo
GROUP BY: Agrupar para Comparar
Las funciones de agregación son más poderosas cuando se aplican por grupo. Carlos quiere saber cuántos productos tiene cada categoría:
SELECT c.nombre_categoria,COUNT(p.producto_id) AS cantidad_productos,AVG(p.precio_venta) AS precio_promedio,SUM(p.precio_venta * p.stock) AS valor_inventarioFROM productos pJOIN categorias c ON p.categoria_id = c.categoria_idGROUPBY c.nombre_categoriaORDERBY valor_inventario DESC;
GROUP BY: Agrupar para Comparar
Regla clave: en un SELECT con GROUP BY, cada columna que no es una función de agregación debe aparecer en el GROUP BY.
graph LR
A["Tabla productos\n(10 filas)"] --> B["GROUP BY categoria"]
B --> C["Zapatillas: 3 filas"]
B --> D["Poleras: 4 filas"]
B --> E["Accesorios: 3 filas"]
C --> F["COUNT=3, AVG=89.990"]
D --> G["COUNT=4, AVG=24.990"]
E --> H["COUNT=3, AVG=12.990"]
HAVING: Filtrar Grupos
WHERE filtra filas antes de agrupar. HAVING filtra grupos después de agrupar.
Roberto quiere ver solo las categorías con valor de inventario superior a $500.000:
SELECT c.nombre_categoria,COUNT(p.producto_id) AS cantidad_productos,SUM(p.precio_venta * p.stock) AS valor_inventarioFROM productos pJOIN categorias c ON p.categoria_id = c.categoria_idGROUPBY c.nombre_categoriaHAVINGSUM(p.precio_venta * p.stock) >500000ORDERBY valor_inventario DESC;
¿Por qué no usar WHERE aquí?
-- ❌ Esto falla — WHERE no puede usar funciones de agregaciónWHERESUM(precio_venta * stock) >500000-- ✅ HAVING es la cláusula correcta para filtrar resultados agregadosHAVINGSUM(precio_venta * stock) >500000
Regla mnemotécnica: WHERE filtra filas individuales; HAVING filtra el resultado de los grupos.
🚀 Desafío Rápido 2
Usando GROUP BY y HAVING, responde las preguntas de TechStyle:
Pregunta A: ¿Cuántas ventas se realizaron en cada mes de 2026? (Pista: MONTH(fecha) extrae el mes de una fecha)
Pregunta B: ¿Qué categorías tienen más de 2 productos en el catálogo?
Pregunta C — Desafío: Roberto quiere ver los clientes que tienen más de 1 venta registrada, con el total de ventas por cliente. (Tablas: ventas, clientes)
JOIN: El Puente Entre Tablas
En el laboratorio W07 ya usaste JOINs. Hoy los entendemos formalmente.
¿Por qué necesitamos JOINs? Porque los datos están distribuidos en tablas separadas para evitar redundancia — y necesitamos combinarlos para responder preguntas de negocio.
graph LR
A["ventas\nventa_id | cliente_id | fecha"] -->|"JOIN ON\ncliente_id"| C["Resultado\nventa_id | nombre | fecha"]
B["clientes\ncliente_id | nombre | apellido"] -->|"JOIN ON\ncliente_id"| C
style C fill:#d4edda,stroke:#28a745
Sintaxis básica del INNER JOIN:
SELECT v.venta_id, c.nombre, c.apellido, v.fechaFROM ventas vINNERJOIN clientes c ON v.cliente_id = c.cliente_idORDERBY v.fecha DESC;
INNER JOIN retorna solo las filas que tienen coincidencia en ambas tablas.
LEFT JOIN: Incluir los que No Tienen Contraparte
Carlos necesita ver todas las ventas, incluyendo las que aún no tienen despacho asignado:
-- INNER JOIN: solo ventas CON despachoSELECT v.venta_id, v.fecha, d.estadoFROM ventas vINNERJOIN despachos d ON v.venta_id = d.venta_id;-- LEFT JOIN: TODAS las ventas, con o sin despachoSELECT v.venta_id, v.fecha, d.estado AS estado_despacho, IFNULL(d.estado, 'Sin despacho') AS estado_legibleFROM ventas vLEFTJOIN despachos d ON v.venta_id = d.venta_id;
Diferencia clave:
INNER JOIN
LEFT JOIN
Ventas con despacho
✅ Incluye
✅ Incluye
Ventas sin despacho
❌ Excluye
✅ Incluye (con NULL)
IFNULL(columna, 'texto') reemplaza los NULL con un valor legible en el reporte.
JOIN con Múltiples Tablas
Para responder preguntas complejas, se encadenan varios JOINs. Roberto quiere el detalle completo de cada venta:
SELECT v.venta_id, v.fecha, c.nombre AS cliente, c.region, p.nombre_producto AS producto, dv.cantidad, dv.precio_unitario, dv.cantidad * dv.precio_unitario AS subtotalFROM ventas vJOIN clientes c ON v.cliente_id = c.cliente_idJOIN detalle_ventas dv ON v.venta_id = dv.venta_idJOIN productos p ON dv.producto_id = p.producto_idORDERBY v.fecha DESC, v.venta_id;
Estrategia para construir un JOIN múltiple:
Identificar la tabla central (normalmente la tabla de hechos: ventas, detalle_ventas).
Agregar cada JOIN necesario para traer los atributos descriptivos (nombre del cliente, nombre del producto).
Verificar que la condición ON usa las FK correctas.
Construyendo el Reporte de Roberto
La pregunta original: ¿cuáles son los productos más vendidos?
SELECT p.nombre_producto, cat.nombre_categoria,SUM(dv.cantidad) AS unidades_vendidas,SUM(dv.cantidad * dv.precio_unitario) AS ingresos_totales,AVG(dv.precio_unitario) AS precio_promedio_ventaFROM detalle_ventas dvJOIN productos p ON dv.producto_id = p.producto_idJOIN categorias cat ON p.categoria_id = cat.categoria_idGROUPBY p.producto_id, p.nombre_producto, cat.nombre_categoriaORDERBY ingresos_totales DESCLIMIT5;
Esta consulta combina todo lo visto hoy:
JOIN para cruzar detalle de ventas con productos y categorías.
SUM y AVG para calcular métricas por producto.
GROUP BY para obtener un resultado por producto.
ORDER BY + LIMIT para obtener el top 5.
Este resultado puede exportarse directamente a un dashboard en Power BI o Excel — exactamente lo que Roberto necesita para su CMI.
Conexión con la Toma de Decisiones
¿Por qué le importa todo esto a Roberto y María?
Una base de datos relacional sin consultas es solo almacenamiento. Las consultas SQL son lo que transforma los datos en información accionable.
WHERE implementa el criterio de decisión: “muéstrame solo lo que importa”. GROUP BY implementa la perspectiva analítica: “compara por región, por categoría, por período”.
El mismo dato (precio_unitario en detalle_ventas) responde múltiples preguntas: margen, ranking de productos, tendencia de precios — dependiendo de cómo se construya la consulta.
Un analista que sabe combinar JOIN + GROUP BY + HAVING puede reemplazar semanas de trabajo manual en Excel con una consulta de 10 líneas.
La base de datos no decide sola — pero un analista con SQL puede hacer que los datos hablen.
Puntos Clave de la Semana 8
SELECT … FROM … WHERE … ORDER BY es la estructura base de toda consulta; LIMIT acota el resultado a los primeros N registros.
WHERE filtra filas individuales antes de cualquier cálculo; sus operadores incluyen =, BETWEEN, IN, LIKE e IS NULL.
Las funciones de agregación (COUNT, SUM, AVG, MAX, MIN) resumen múltiples filas en un único valor por grupo.
GROUP BY segmenta el resultado por categoría, región, período u otra dimensión; HAVING filtra esos grupos después del cálculo.
INNER JOIN combina filas que tienen coincidencia en ambas tablas; LEFT JOIN incluye todas las filas de la tabla izquierda, con NULL donde no hay coincidencia.
Para construir un reporte complejo: identifica la tabla de hechos, agrega los JOINs necesarios para los atributos descriptivos, aplica la agregación y filtra con HAVING.
Preview del Laboratorio W08
Lab W08: Reportes de Negocio para TechStyle
Responderás las preguntas de Roberto, María y Carlos con consultas SQL reales sobre la base de datos techstyle que construiste en W04–W07.
Practicarás WHERE con condiciones múltiples, funciones de fecha y operadores de texto.
Construirás reportes agregados con GROUP BY y HAVING: ventas por categoría, clientes por segmento, despachos por estado.
Usarás INNER JOIN y LEFT JOIN para cruzar tablas y generar el detalle completo de ventas, incluyendo productos sin movimiento.
Al finalizar, tendrás un set de 8 consultas documentadas que sirven como base para el reporte final de la Solemne 2.
En W09 completaremos el arsenal: subconsultas, JOINs con agregación avanzada y la preparación del proyecto de Solemne 2.