4to cuatrimestre Programación Web I Base de datos SQL Avanzado I Repaso primer parcial

Repaso general de Base de Datos y SQL Avanzado I

En esta clase vamos a practicar consultas SQL usando la base de ejemplo de empresa: empleados, supervisores, clientes, tipos de cliente, productos, categorías, ventas y detalle de ventas.

1) Objetivo del repaso

El objetivo es reforzar consultas que combinan varias tablas y que responden preguntas reales de un sistema. No se trata solamente de escribir SQL largo, sino de entender qué información necesitamos, de qué tablas sale y cómo se relacionan esas tablas.

Temas que se repasan:
relaciones entre tablas, llaves primarias, llaves foráneas, INNER JOIN, LEFT JOIN, WHERE, LIKE, GROUP BY, COUNT, SUM, AVG, HAVING, ORDER BY y subconsultas.
Nota:
En estas soluciones no usamos funciones extra como COALESCE, porque todavía no se están repasando aquí. Si una suma o promedio no tiene datos relacionados, MySQL puede mostrar NULL. Eso significa que no hubo registros relacionados para calcular ese dato.

2) Base de datos que se usará

Los ejercicios usan la base:

empresa_sql_avanzado_1

Y las tablas principales son:

departamentos empleados tipos_cliente clientes categorias_producto productos ventas venta_detalles
Importante:
El script para agregar más datos no está dentro de esta tarjeta. Ese SQL se ejecuta aparte antes de resolver los ejercicios.

3) Diagrama de tablas y relaciones

Este diagrama muestra la estructura general de la base de datos de práctica. Cada cuadro representa una tabla. Los campos marcados como PK son llaves primarias y los campos marcados como FK son llaves foráneas.

Idea importante:
Este tipo de diagrama nos ayuda a entender de dónde sale la información antes de escribir consultas SQL. Si no entendemos las relaciones, los JOIN se vuelven adivinanzas.
departamentos
  • PK id
  • nombre
  • activo
empleados
  • PK id
  • FK departamento_id
  • FK supervisor_id
  • nombre
  • puesto
  • salario
  • fecha_ingreso
  • activo
tipos_cliente
  • PK id
  • nombre
  • descuento_base
  • activo
clientes
  • PK id
  • FK tipo_cliente_id
  • nombre
  • correo
  • telefono
  • ciudad
  • activo
categorias_producto
  • PK id
  • nombre
  • activo
productos
  • PK id
  • FK categoria_id
  • codigo
  • nombre
  • precio
  • costo
  • existencia
  • activo
ventas
  • PK id
  • FK cliente_id
  • FK vendedor_id
  • folio
  • fecha_venta
  • subtotal
  • descuento
  • total
  • estado
venta_detalles
  • PK id
  • FK venta_id
  • FK producto_id
  • cantidad
  • precio_unitario
  • descuento_linea
  • total_linea

Relaciones principales

departamentos.id
Un departamento
1 → N
empleados.departamento_id
Muchos empleados
empleados.id
Un supervisor
1 → N
empleados.supervisor_id
Muchos empleados supervisados
tipos_cliente.id
Un tipo de cliente
1 → N
clientes.tipo_cliente_id
Muchos clientes
categorias_producto.id
Una categoría
1 → N
productos.categoria_id
Muchos productos
clientes.id
Un cliente
1 → N
ventas.cliente_id
Muchas ventas
empleados.id
Un vendedor
1 → N
ventas.vendedor_id
Muchas ventas
ventas.id
Una venta
1 → N
venta_detalles.venta_id
Muchos renglones de venta
productos.id
Un producto
1 → N
venta_detalles.producto_id
Muchas apariciones en ventas
Idea central:
Una venta se divide en dos partes: el encabezado de la venta está en ventas y los productos vendidos están en venta_detalles.

4) Ejercicios de repaso

Resuelve los siguientes ejercicios en DBeaver o en el gestor de base de datos. Son ejercicios nuevos, pero usan los mismos conceptos que ya repasamos. Intenta resolverlos primero sin ver la respuesta.

Ejercicio 1: vendedores y monto vendido por ciudad

Mostrar el nombre del vendedor, la ciudad del cliente, cuántas ventas cerradas hizo en esa ciudad y cuánto vendió en total.

Ordena el resultado por vendedor y después por total vendido de mayor a menor.

Pistas:
Usa ventas, empleados y clientes. Agrupa por vendedor y ciudad.

Ejercicio 2: categorías con ventas mayores a 5000

Mostrar cada categoría de producto, cuántas unidades se vendieron y cuánto dinero generó.

Solo deben aparecer categorías cuyo total vendido sea mayor a 5000.

Pistas:
Usa categorias_producto, productos y venta_detalles. Para filtrar después de agrupar usa HAVING.

Ejercicio 3: productos arriba del promedio de precio de su categoría

Mostrar los productos cuyo precio sea mayor al precio promedio de los productos de su misma categoría.

El resultado debe mostrar categoría, código, producto, precio y promedio de su categoría.

Pistas:
Usa una subconsulta que calcule el promedio usando la misma categoria_id del producto que se está revisando.

Ejercicio 4: clientes sin compras cerradas

Mostrar los clientes que no tienen ninguna venta cerrada.

El resultado debe mostrar tipo de cliente, nombre del cliente y ciudad.

Pistas:
Usa LEFT JOIN entre clientes y ventas. Después filtra donde no exista venta relacionada.

Apoyo: orden correcto de una consulta SQL

Antes de revisar las respuestas, es importante recordar que las partes de una consulta SQL tienen un orden. Si cambiamos ese orden, MySQL puede marcar error aunque la idea de la consulta sea correcta.

1. SELECT qué columnas quiero mostrar 2. FROM de qué tabla principal parto 3. JOIN qué otras tablas conecto 4. WHERE qué filas filtro antes de agrupar 5. GROUP BY cómo agrupo los datos 6. HAVING qué grupos filtro después de agrupar 7. ORDER BY cómo ordeno el resultado final 8. LIMIT cuántos resultados quiero ver
Regla rápida:
WHERE filtra filas normales antes de hacer grupos.
HAVING filtra grupos después de usar GROUP BY.
ORDER BY ordena el resultado final.

Ejemplo correcto

SELECT
  e.nombre AS vendedor,
  COUNT(v.id) AS cantidad_ventas,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE v.estado = 'cerrada'
GROUP BY
  e.id,
  e.nombre
HAVING COUNT(v.id) > 2
ORDER BY total_vendido DESC;

Lectura de la consulta:

FROM ventas → parto de la tabla ventas INNER JOIN empleados → conecto ventas con empleados para saber el vendedor WHERE v.estado = 'cerrada' → primero filtro solo ventas cerradas GROUP BY e.id, e.nombre → agrupo las ventas por vendedor COUNT(v.id) y SUM(v.total) → calculo cantidad de ventas y total vendido HAVING COUNT(v.id) > 2 → dejo solo vendedores con más de 2 ventas ORDER BY total_vendido DESC → ordeno del que más vendió al que menos

Error común 1: poner HAVING antes de GROUP BY

SELECT
  e.nombre AS vendedor,
  COUNT(v.id) AS cantidad_ventas
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE v.estado = 'cerrada'
HAVING COUNT(v.id) > 2
GROUP BY
  e.id,
  e.nombre;
¿Por qué está mal?
Porque HAVING necesita que los grupos ya existan. Primero debe ir GROUP BY y después HAVING.

Error común 2: usar SUM, COUNT o AVG dentro de WHERE

SELECT
  e.nombre AS vendedor,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE SUM(v.total) > 10000
GROUP BY
  e.id,
  e.nombre;
¿Por qué está mal?
Porque WHERE ocurre antes de agrupar. En ese momento todavía no existe el resultado de SUM(v.total). Para filtrar por una suma, se usa HAVING.

Corrección del error anterior

SELECT
  e.nombre AS vendedor,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
GROUP BY
  e.id,
  e.nombre
HAVING SUM(v.total) > 10000
ORDER BY total_vendido DESC;
Frase para recordar:
Si filtro antes de sumar o contar, uso WHERE.
Si filtro después de sumar o contar, uso HAVING.

5) Ver ejercicios resueltos

Las soluciones se pueden mostrar con contraseña.

Solución ejercicio 1: vendedores y monto vendido por ciudad

SELECT
  e.nombre AS vendedor,
  c.ciudad,
  COUNT(v.id) AS ventas_cerradas,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
INNER JOIN clientes c ON c.id = v.cliente_id
WHERE v.estado = 'cerrada'
GROUP BY
  e.id,
  e.nombre,
  c.ciudad
ORDER BY
  e.nombre ASC,
  total_vendido DESC;
Explicación:
La consulta parte de ventas porque queremos analizar ventas cerradas. Con INNER JOIN se obtiene el nombre del vendedor y la ciudad del cliente. Luego se agrupa por vendedor y ciudad para calcular cantidad de ventas y total vendido.

Solución ejercicio 2: categorías con ventas mayores a 5000

SELECT
  cp.nombre AS categoria,
  SUM(vd.cantidad) AS unidades_vendidas,
  SUM(vd.total_linea) AS total_generado
FROM categorias_producto cp
INNER JOIN productos p ON p.categoria_id = cp.id
INNER JOIN venta_detalles vd ON vd.producto_id = p.id
GROUP BY
  cp.id,
  cp.nombre
HAVING SUM(vd.total_linea) > 5000
ORDER BY total_generado DESC;
Explicación:
Primero se juntan categorías, productos y detalle de ventas. Después se agrupa por categoría. Como el filtro depende de una suma, se usa HAVING y no WHERE.

Solución ejercicio 3: productos arriba del promedio de precio de su categoría

SELECT
  cp.nombre AS categoria,
  p.codigo,
  p.nombre AS producto,
  p.precio,
  (
    SELECT AVG(p2.precio)
    FROM productos p2
    WHERE p2.categoria_id = p.categoria_id
  ) AS promedio_categoria
FROM productos p
INNER JOIN categorias_producto cp ON cp.id = p.categoria_id
WHERE p.precio > (
  SELECT AVG(p2.precio)
  FROM productos p2
  WHERE p2.categoria_id = p.categoria_id
)
ORDER BY
  cp.nombre ASC,
  p.precio DESC;
Explicación:
La subconsulta calcula el promedio de precio de la categoría del producto actual. Después la consulta principal muestra solo los productos cuyo precio es mayor a ese promedio.

Solución ejercicio 4: clientes sin compras cerradas

SELECT
  tc.nombre AS tipo_cliente,
  c.nombre AS cliente,
  c.ciudad
FROM clientes c
INNER JOIN tipos_cliente tc ON tc.id = c.tipo_cliente_id
LEFT JOIN ventas v
  ON v.cliente_id = c.id
  AND v.estado = 'cerrada'
WHERE v.id IS NULL
ORDER BY
  tc.nombre ASC,
  c.nombre ASC;
Explicación:
El LEFT JOIN permite listar clientes aunque no tengan ventas cerradas. Cuando no hay venta relacionada, v.id queda vacío. Por eso usamos WHERE v.id IS NULL para quedarnos con clientes sin compras cerradas.

5) Apoyo: orden correcto de una consulta SQL

Antes de revisar las respuestas, es importante recordar que las partes de una consulta SQL tienen un orden. Si cambiamos ese orden, MySQL puede marcar error aunque la idea de la consulta sea correcta.

1. SELECT qué columnas quiero mostrar 2. FROM de qué tabla principal parto 3. JOIN qué otras tablas conecto 4. WHERE qué filas filtro antes de agrupar 5. GROUP BY cómo agrupo los datos 6. HAVING qué grupos filtro después de agrupar 7. ORDER BY cómo ordeno el resultado final 8. LIMIT cuántos resultados quiero ver
Regla rápida:
WHERE filtra filas normales antes de hacer grupos.
HAVING filtra grupos después de usar GROUP BY.
ORDER BY ordena el resultado final.

Ejemplo correcto

SELECT
  e.nombre AS vendedor,
  COUNT(v.id) AS cantidad_ventas,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE v.estado = 'cerrada'
GROUP BY
  e.id,
  e.nombre
HAVING COUNT(v.id) > 2
ORDER BY total_vendido DESC;

Lectura de la consulta:

FROM ventas → parto de la tabla ventas INNER JOIN empleados → conecto ventas con empleados para saber el vendedor WHERE v.estado = 'cerrada' → primero filtro solo ventas cerradas GROUP BY e.id, e.nombre → agrupo las ventas por vendedor COUNT(v.id) y SUM(v.total) → calculo cantidad de ventas y total vendido HAVING COUNT(v.id) > 2 → dejo solo vendedores con más de 2 ventas ORDER BY total_vendido DESC → ordeno del que más vendió al que menos

Error común 1: poner HAVING antes de GROUP BY

SELECT
  e.nombre AS vendedor,
  COUNT(v.id) AS cantidad_ventas
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE v.estado = 'cerrada'
HAVING COUNT(v.id) > 2
GROUP BY
  e.id,
  e.nombre;
¿Por qué está mal?
Porque HAVING necesita que los grupos ya existan. Primero debe ir GROUP BY y después HAVING.

Error común 2: usar SUM, COUNT o AVG dentro de WHERE

SELECT
  e.nombre AS vendedor,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
WHERE SUM(v.total) > 10000
GROUP BY
  e.id,
  e.nombre;
¿Por qué está mal?
Porque WHERE ocurre antes de agrupar. En ese momento todavía no existe el resultado de SUM(v.total). Para filtrar por una suma, se usa HAVING.

Corrección del error anterior

SELECT
  e.nombre AS vendedor,
  SUM(v.total) AS total_vendido
FROM ventas v
INNER JOIN empleados e ON e.id = v.vendedor_id
GROUP BY
  e.id,
  e.nombre
HAVING SUM(v.total) > 10000
ORDER BY total_vendido DESC;
Frase para recordar:
Si filtro antes de sumar o contar, uso WHERE.
Si filtro después de sumar o contar, uso HAVING.

6) Cierre

En este repaso practicamos consultas que combinan varias ideas: relaciones, filtros, agrupaciones, cálculos y subconsultas.

Para recordar:
SQL avanzado no significa escribir consultas enormes. Significa saber responder preguntas reales usando correctamente la estructura de la base de datos.