↩️ index
Guía 02 - Caso Heladería: DDL, DML y Consultas (QUERIES)
Entorno: VM Ubuntu + LAMP + phpMyAdmin + MariaDB 10 / Cliente CLI
mysql
1. Contexto del Ejercicio
El dueño de una cadena local de heladerías ha estado registrando sus ventas en una hoja de cálculo. Sin embargo, a medida que el negocio ha crecido, se ha encontrado con problemas graves:
- Control de inventario y demanda: No sabe con exactitud qué sabores se venden más y cuáles están estancados.
- Fidelización: Los datos de sus clientes están mezclados con las ventas, impidiéndole identificar quiénes son sus compradores frecuentes para ofrecerles promociones.
- Rendimiento por sucursal: Le cuesta trabajo comparar las ventas entre sus diferentes locales.
Para solucionar esto, hemos diseñado un modelo relacional básico. En esta guía, serás el encargado de implementar la base de datos, poblarla con datos históricos simulados y ejecutar las consultas estratégicas que el dueño necesita para tomar decisiones.
2. Creación de la Estructura (DDL)
💡 Nota de Buenas Prácticas: En el siguiente script notarás que las tablas tienen el prefijo
Table(ej.TableClientes). Aunque funcionalmente correcto, en entornos profesionales se recomienda nombrar las tablas de forma directa (ej.clientes,sabores,ventas) para mantener el código limpio. Mantendremos la estructura actual por motivos didácticos.
Conéctate a MariaDB y ejecuta el siguiente script para construir el esquema relacional (mydb). Puedes hacerlo desde la terminal (mysql -u root -p) o desde la pestaña SQL de phpMyAdmin.
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 ;
USE `mydb` ;
-- Tabla Locales
CREATE TABLE IF NOT EXISTS `mydb`.`TableLocal` (
`idTableLocal` INT NOT NULL AUTO_INCREMENT,
`LocalCiudad` VARCHAR(45) NULL,
`LocalSector` VARCHAR(45) NULL,
PRIMARY KEY (`idTableLocal`))
ENGINE = InnoDB;
-- Tabla Clientes
CREATE TABLE IF NOT EXISTS `mydb`.`TableClientes` (
`idTableClientes` INT NOT NULL AUTO_INCREMENT,
`Nombre` VARCHAR(45) NOT NULL,
`Telefono` VARCHAR(10) NULL,
PRIMARY KEY (`idTableClientes`))
ENGINE = InnoDB;
-- Tabla Ventas
CREATE TABLE IF NOT EXISTS `mydb`.`TableVenta` (
`idTableVenta` INT NOT NULL AUTO_INCREMENT,
`TableClientes_idTableClientes` INT NOT NULL,
`TableLocal_idTableLocal` INT NOT NULL,
`TipoEnvase` VARCHAR(45) NULL,
`FechaVenta` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`idTableVenta`),
INDEX `fk_TableVenta_Local_idx` (`TableLocal_idTableLocal` ASC) VISIBLE,
INDEX `fk_TableVenta_Clientes_idx` (`TableClientes_idTableClientes` ASC) VISIBLE,
CONSTRAINT `fk_TableVenta_Local`
FOREIGN KEY (`TableLocal_idTableLocal`) REFERENCES `mydb`.`TableLocal` (`idTableLocal`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_TableVenta_Clientes`
FOREIGN KEY (`TableClientes_idTableClientes`) REFERENCES `mydb`.`TableClientes` (`idTableClientes`) ON DELETE RESTRICT ON UPDATE CASCADE)
ENGINE = InnoDB;
-- Tabla Sabores
CREATE TABLE IF NOT EXISTS `mydb`.`TableSabores` (
`idTableSabores` INT NOT NULL AUTO_INCREMENT,
`NombreSabor` VARCHAR(45) NULL,
`PrecioUnitarioSabor` INT NULL,
PRIMARY KEY (`idTableSabores`))
ENGINE = InnoDB;
-- Tabla Detalle de Ventas
CREATE TABLE IF NOT EXISTS `mydb`.`TableDetalleVentas` (
`TableVenta_idTableVenta` INT NOT NULL,
`TableSabores_idTableSabores` INT NOT NULL,
`Cantidad` INT NULL,
PRIMARY KEY (`TableVenta_idTableVenta`, `TableSabores_idTableSabores`),
INDEX `fk_Detalle_Sabores_idx` (`TableSabores_idTableSabores` ASC) VISIBLE,
INDEX `fk_Detalle_Venta_idx` (`TableVenta_idTableVenta` ASC) VISIBLE,
CONSTRAINT `fk_Detalle_Sabores`
FOREIGN KEY (`TableSabores_idTableSabores`) REFERENCES `mydb`.`TableSabores` (`idTableSabores`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_Detalle_Venta`
FOREIGN KEY (`TableVenta_idTableVenta`) REFERENCES `mydb`.`TableVenta` (`idTableVenta`) ON DELETE CASCADE ON UPDATE CASCADE)
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;3. Carga de Datos Semilla (DML - INSERT)
Para que las consultas tengan sentido, cargaremos información inicial: locales, sabores, clientes y un historial de 50 ventas con sus respectivos detalles.
Asegúrate de estar usando la base de datos correcta (USE mydb;) y ejecuta:
USE `mydb`;
-- 1. Locales
INSERT INTO `TableLocal` (`LocalCiudad`, `LocalSector`) VALUES
('Bogotá', 'Norte'), ('Cajicá', 'Sur'), ('Chía', 'Sur'), ('Bogotá', 'Sur'), ('Zipaquirá', 'Centro');
-- 2. Sabores
INSERT INTO `TableSabores` (`NombreSabor`, `PrecioUnitarioSabor`) VALUES
('Vainilla', 5000), ('Chocolate', 3000), ('Fresa', 6000), ('Maracuya', 4000),
('Chococherry', 9000), ('Arequipe', 1000), ('Limon', 4000), ('Cookies and cream', 11000),
('Pistacho', 2000), ('Frutas del bosque', 5000);
-- 3. Clientes (15 clientes)
INSERT INTO `TableClientes` (`Nombre`, `Telefono`) VALUES
('Ana García', '5550101'), ('Luis Páez', '5550202'), ('Marcelo Sotaminga', '3174751858'),
('Mauricio Berbeo', '3152283523'), ('Laura Triana', '3133134987'), ('Carlos Restrepo', '3101234567'),
('Diana Rojas', '3209876543'), ('Andrés Jaramillo', '3114567890'), ('Camila Vargas', '3123456789'),
('Jorge Mendoza', '3145678901'), ('Valentina Gómez', '3187654321'), ('Santiago Castro', '3198765432'),
('Paula Medina', '3165432109'), ('Felipe Ortiz', '3001122334'), ('Natalia Silva', '3012233445');
-- 4. Ventas (Cabeceras) - 50 registros distribuidos entre clientes y locales
INSERT INTO `TableVenta` (`TableClientes_idTableClientes`, `TableLocal_idTableLocal`, `TipoEnvase`, `FechaVenta`) VALUES
(1, 1, 'Cono', '2026-03-01 14:30:00'), (3, 2, 'Vaso', '2026-03-01 15:45:00'), (5, 1, 'Cono', '2026-03-02 10:15:00'),
(2, 3, 'Cono', '2026-03-02 16:20:00'), (4, 4, 'Vaso', '2026-03-03 11:10:00'), (7, 5, 'Cono', '2026-03-03 18:05:00'),
(6, 1, 'Cono', '2026-03-04 13:40:00'), (8, 2, 'Vaso', '2026-03-04 19:20:00'), (10, 3, 'Cono', '2026-03-05 12:50:00'),
(9, 4, 'Cono', '2026-03-05 17:35:00'), (12, 5, 'Vaso', '2026-03-06 14:15:00'), (11, 1, 'Cono', '2026-03-06 16:40:00'),
(15, 2, 'Cono', '2026-03-07 10:25:00'), (13, 3, 'Vaso', '2026-03-07 15:55:00'), (14, 4, 'Cono', '2026-03-08 11:30:00'),
(1, 5, 'Vaso', '2026-03-08 18:45:00'), (3, 1, 'Cono', '2026-03-09 13:10:00'), (5, 2, 'Cono', '2026-03-09 17:05:00'),
(2, 3, 'Vaso', '2026-03-10 12:20:00'), (4, 4, 'Cono', '2026-03-10 16:15:00'), (7, 5, 'Cono', '2026-03-11 14:50:00'),
(6, 1, 'Vaso', '2026-03-11 19:35:00'), (8, 2, 'Cono', '2026-03-12 10:40:00'), (10, 3, 'Cono', '2026-03-12 15:25:00'),
(9, 4, 'Vaso', '2026-03-13 11:55:00'), (12, 5, 'Cono', '2026-03-13 18:10:00'), (11, 1, 'Cono', '2026-03-14 13:20:00'),
(15, 2, 'Vaso', '2026-03-14 17:45:00'), (13, 3, 'Cono', '2026-03-15 12:15:00'), (14, 4, 'Cono', '2026-03-15 16:50:00'),
(1, 1, 'Vaso', '2026-03-16 14:05:00'), (3, 2, 'Cono', '2026-03-16 19:15:00'), (5, 3, 'Cono', '2026-03-17 10:30:00'),
(2, 4, 'Vaso', '2026-03-17 15:40:00'), (4, 5, 'Cono', '2026-03-18 11:25:00'), (7, 1, 'Cono', '2026-03-18 18:35:00'),
(6, 2, 'Vaso', '2026-03-19 13:50:00'), (8, 3, 'Cono', '2026-03-19 17:10:00'), (10, 4, 'Cono', '2026-03-20 12:45:00'),
(9, 5, 'Vaso', '2026-03-20 16:20:00'), (12, 1, 'Cono', '2026-03-21 14:10:00'), (11, 2, 'Cono', '2026-03-21 19:55:00'),
(15, 3, 'Vaso', '2026-03-22 11:15:00'), (13, 4, 'Cono', '2026-03-22 15:30:00'), (14, 5, 'Cono', '2026-03-23 10:45:00'),
(1, 1, 'Vaso', '2026-03-23 18:00:00'), (3, 2, 'Cono', '2026-03-24 13:25:00'), (5, 3, 'Cono', '2026-03-24 17:40:00'),
(2, 4, 'Vaso', '2026-03-25 12:55:00'), (4, 5, 'Cono', '2026-03-25 16:10:00');
-- 5. Detalle de Ventas (Asociando ventas con sabores y cantidades)
INSERT INTO `TableDetalleVentas` (`TableVenta_idTableVenta`, `TableSabores_idTableSabores`, `Cantidad`) VALUES
(1, 2, 2), (1, 8, 1), (2, 3, 1), (3, 1, 3), (3, 5, 1), (4, 4, 2), (5, 9, 1), (5, 7, 1),
(6, 10, 2), (7, 6, 4), (8, 2, 2), (8, 3, 1), (9, 8, 1), (10, 1, 2), (10, 4, 2), (11, 5, 1),
(12, 9, 3), (13, 7, 2), (13, 10, 1), (14, 6, 1), (15, 2, 3), (16, 3, 2), (17, 8, 4),
(18, 1, 1), (18, 4, 1), (19, 5, 2), (20, 9, 1), (20, 7, 3), (21, 10, 2), (22, 6, 1),
(22, 2, 1), (23, 3, 3), (24, 8, 2), (25, 1, 1), (26, 4, 2), (26, 5, 1), (27, 9, 2),
(28, 7, 1), (29, 10, 3), (29, 6, 1), (30, 2, 2), (31, 3, 1), (31, 8, 2), (32, 1, 4),
(33, 4, 1), (34, 5, 2), (35, 9, 1), (36, 7, 2), (36, 10, 2), (37, 6, 1), (38, 2, 3),
(39, 3, 2), (40, 8, 1), (40, 1, 1), (41, 4, 3), (42, 5, 2), (43, 9, 1), (44, 7, 4),
(45, 10, 1), (45, 6, 1), (46, 2, 2), (47, 3, 1), (48, 8, 3), (48, 1, 1), (49, 4, 2),
(50, 5, 1), (50, 9, 2);4. Fundamentos Teóricos: SQL Intermedio y DML
Antes de realizar operaciones o consultas complejas, es importante asentar conceptos clave del SQL Intermedio (basado en la teoría de bases de datos relacionales, como el capítulo 4 de Silberschatz et al.).
Integridad Referencial y Transacciones
- Integridad Referencial (Restricciones): En el DDL usamos
ON DELETE RESTRICTyON DELETE CASCADE. Esto asegura que no puedas borrar un sabor si hay ventas que lo usan (RESTRICT), pero si borras una venta, se borra automáticamente su detalle (CASCADE). - Transacciones: Las operaciones DML (inserciones, actualizaciones, borrados) modifican el estado de la base de datos. En entornos críticos, se agrupan en transacciones usando
START TRANSACTION, y se confirman conCOMMITo se deshacen conROLLBACKen caso de error o arrepentimiento antes de confirmar.
Lenguaje de Manipulación de Datos (DML)
El DML no es solo insertar (INSERT); el mantenimiento de los datos es clave:
UPDATE: Modifica registros existentes. ¡Siempre debe llevar una cláusulaWHEREpara evitar modificar toda la tabla!DELETE: Elimina registros. De nuevo, la cláusulaWHEREes obligatoria para evitar vaciar la tabla.
Teoría de Conjuntos y Vistas: JOINs
Para responder preguntas del negocio cruzando información, unimos tablas usando JOIN.
- INNER JOIN: Trae únicamente los registros donde hay coincidencia en ambas tablas.
- LEFT / RIGHT OUTER JOIN: Trae TODOS los registros de la tabla izquierda/derecha. Si no hay coincidencias, llena los vacíos con
NULL. - Vistas (Views): Consultas que se guardan como tablas virtuales (ej.
CREATE VIEW vista_ventas AS SELECT ...). Ayudan a simplificar queries recurrentes y a ocultar complejidad.
Agrupación (GROUP BY y HAVING)
WHEREfiltra filas individuales antes de agrupar.HAVINGfiltra grupos después de haber aplicado las funciones de agregación (SUM,COUNT,AVG).
Orden lógico de ejecución de SQL vs. Sintaxis de escritura:
Es vital entender que no escribimos las consultas en el mismo orden en el que el motor de base de datos las procesa.
- Orden de escritura (Sintaxis):
SELECT➔FROM➔JOIN➔WHERE➔GROUP BY➔HAVING➔ORDER BY➔LIMIT- Orden de ejecución (Lógica del motor):
FROMyJOIN: Primero, el motor busca de dónde sacar los datos y cómo cruzar las tablas.WHERE: Luego, filtra las filas que cumplen la condición.GROUP BY: Después, agrupa las filas restantes en bloques.HAVING: Filtra esos grupos creados (por ej. descartar grupos muy pequeños).SELECT: Recién en este punto extrae las columnas exactas a mostrar.ORDER BY: Ordena el resultado final.LIMIT: Corta la cantidad de resultados devueltos al final.
5. Ejercicios Prácticos de DML (Actualizaciones y Borrados)
El dueño te pide ajustar ciertos datos operativos mediante sentencias UPDATE y DELETE. Aquí tienes 10 ejercicios con sus soluciones.
Ejercicio 1: Actualización de precios (Inflación)
El precio de importación del Pistacho subió. Debes actualizar el precio unitario del sabor “Pistacho” incrementándolo en 1000 pesos.
UPDATE TableSabores
SET PrecioUnitarioSabor = PrecioUnitarioSabor + 1000
WHERE NombreSabor = 'Pistacho';Ejercicio 2: Corrección de un registro
El local registrado en la ciudad de “Bogotá” y sector “Sur” tuvo un error tipográfico en el sector y debe cambiarse a “Occidente”.
UPDATE TableLocal
SET LocalSector = 'Occidente'
WHERE LocalCiudad = 'Bogotá' AND LocalSector = 'Sur';Ejercicio 3: Borrado controlado (Simulación)
Escribe la sentencia para intentar eliminar a la cliente “Natalia Silva”.
Advertencia: El SGBD mostrará un error por la restricción
ON DELETE RESTRICT, protegiendo la integridad, ya que Natalia podría tener compras asociadas.
DELETE FROM TableClientes
WHERE Nombre = 'Natalia Silva';Ejercicio 4: Actualización masiva con condición
Por una promoción especial, todos los sabores que cuesten más de 6000 pesos tendrán un descuento de 500 pesos.
UPDATE TableSabores
SET PrecioUnitarioSabor = PrecioUnitarioSabor - 500
WHERE PrecioUnitarioSabor > 6000;Ejercicio 5: Cambio de formato de teléfono
Se necesita actualizar el teléfono del cliente “Luis Páez”, que cambió de número a “3220001111”.
UPDATE TableClientes
SET Telefono = '3220001111'
WHERE Nombre = 'Luis Páez';Ejercicio 6: Limpieza de datos (Borrado)
Se ha decidido dejar de vender el sabor “Arequipe”. Como no ha tenido ventas, la restricción no nos impedirá borrarlo (si tuviera ventas habría que borrar el detalle primero o cambiar el estado).
DELETE FROM TableSabores
WHERE NombreSabor = 'Arequipe';Ejercicio 7: Inserción de un nuevo producto (DML)
Llegó un nuevo sabor a la heladería: “Menta con chispas”, con un precio de 5500 pesos.
INSERT INTO TableSabores (NombreSabor, PrecioUnitarioSabor)
VALUES ('Menta con chispas', 5500);Ejercicio 8: Actualización de múltiples campos
El cliente “Ana García” se casó y cambió su apellido a “Ana de Silva”, y también cambió su teléfono a “3109998888”.
UPDATE TableClientes
SET Nombre = 'Ana de Silva', Telefono = '3109998888'
WHERE Nombre = 'Ana García';Ejercicio 9: Borrar locales sin actividad (Simulación)
Eliminar el local de “Cajicá” “Sur”. Esto generará un error debido a que ya hay transacciones asociadas a ese local en TableVenta.
DELETE FROM TableLocal
WHERE LocalCiudad = 'Cajicá' AND LocalSector = 'Sur';Ejercicio 10: Traslado de local (Actualización de claves foráneas)
El local de “Zipaquirá” cerró, y todas sus ventas históricas deben reasignarse contablemente al local principal de “Bogotá Norte” (asumiendo que los IDs sean 5 y 1, respectivamente).
UPDATE TableVenta
SET TableLocal_idTableLocal = 1
WHERE TableLocal_idTableLocal = 5;6. Las 10 Consultas Estratégicas del Negocio
A continuación, implementaremos 10 consultas (SELECT) diseñadas para resolver los problemas reales de la heladería.
1. Directorio de clientes básico
El dueño quiere un listado alfabético de sus clientes para una campaña SMS.
SELECT Nombre, Telefono
FROM TableClientes
ORDER BY Nombre ASC;📚 Nota Técnica:
ORDER BYorganiza los resultados.ASCindica orden ascendente (A-Z) yDESCdescendente (Z-A). Si no especificas nada, el motor asumeASCpor defecto.
2. Catálogo de sabores por rango de precio
Mostrar sabores que cuestan entre 4000 y 8000 pesos.
SELECT NombreSabor, PrecioUnitarioSabor
FROM TableSabores
WHERE PrecioUnitarioSabor BETWEEN 4000 AND 8000
ORDER BY PrecioUnitarioSabor DESC;📚 Nota Técnica:
BETWEENes un operador lógico que evalúa si un valor está dentro de un rango inclusivo (incluye el 4000 y el 8000). Es más limpio y legible que escribirPrecioUnitarioSabor >= 4000 AND PrecioUnitarioSabor <= 8000.
3. Historial de una venta específica (Uso de INNER JOIN y Alias)
Mostrar el detalle de la venta #1: qué cliente compró, qué sabor y la cantidad.
SELECT v.idTableVenta, c.Nombre AS Cliente, s.NombreSabor, d.Cantidad
FROM TableVenta v
JOIN TableClientes c ON v.TableClientes_idTableClientes = c.idTableClientes
JOIN TableDetalleVentas d ON v.idTableVenta = d.TableVenta_idTableVenta
JOIN TableSabores s ON d.TableSabores_idTableSabores = s.idTableSabores
WHERE v.idTableVenta = 1;📚 Notas Técnicas sobre Sintaxis:
- Alias de Tabla (
FROM TableVenta v): En lugar de escribir el nombre completo de la tabla en toda la consulta, le asignamos una “letra” o alias (v,c,d,s). Por eso en elSELECTescribimosv.idTableVenta. Esto le dice al motor exactamente de qué tabla sacar la columna, evitando ambigüedades si dos tablas tienen columnas con el mismo nombre.- Alias de Columna (
AS Cliente): UsamosASpara renombrar temporalmente la columna en el resultado visual. En la base de datos la columna sigue llamándoseNombre, pero en la pantalla de resultados aparecerá comoCliente.
4. Ingresos totales generados por Venta (Matemática básica)
Para la venta #10, calcular cuánto dinero ingresó multiplicando cantidad por precio unitario.
SELECT d.TableVenta_idTableVenta AS No_Venta,
s.NombreSabor,
d.Cantidad,
s.PrecioUnitarioSabor,
(d.Cantidad * s.PrecioUnitarioSabor) AS Subtotal
FROM TableDetalleVentas d
JOIN TableSabores s ON d.TableSabores_idTableSabores = s.idTableSabores
WHERE d.TableVenta_idTableVenta = 10;📚 Nota Técnica: SQL permite hacer cálculos matemáticos directamente en la instrucción
SELECT(*,/,+,-). Observa cómo renombramos esa operación al final usandoAS Subtotalpara darle sentido al resultado.
5. Clientes más fieles (Uso de GROUP BY y COUNT)
Identificar a los clientes que han realizado más de 3 compras en la historia de la heladería.
SELECT c.Nombre, COUNT(v.idTableVenta) AS Total_Compras
FROM TableClientes c
JOIN TableVenta v ON c.idTableClientes = v.TableClientes_idTableClientes
GROUP BY c.idTableClientes, c.Nombre
HAVING Total_Compras > 3
ORDER BY Total_Compras DESC;📚 Nota Técnica: Usamos
GROUP BYpara agrupar todas las ventas que le pertenecen al mismo cliente. Luego, la funciónCOUNTcuenta cuántas ventas hay en cada grupo. Finalmente,HAVINGactúa como un “WHERE para grupos”, filtrando y mostrando únicamente aquellos grupos donde el conteo fue mayor a 3.
6. Rendimiento del inventario: Sabores más vendidos
¿Cuál es el sabor que más unidades ha vendido globalmente?
SELECT s.NombreSabor, SUM(d.Cantidad) AS Unidades_Vendidas
FROM TableSabores s
JOIN TableDetalleVentas d ON s.idTableSabores = d.TableSabores_idTableSabores
GROUP BY s.idTableSabores, s.NombreSabor
ORDER BY Unidades_Vendidas DESC
LIMIT 5; -- Mostramos el Top 5📚 Nota Técnica: A diferencia de
COUNT(que cuenta cuántas filas hay),SUMsuma matemáticamente el valor que hay dentro de la columnaCantidad.LIMIT 5le dice al motor de la base de datos que, después de ordenar de mayor a menor, solo nos devuelva las primeras 5 filas (el Top 5).
7. Rendimiento por sucursal
¿Cuántas ventas (tickets) se han emitido en cada local?
SELECT l.LocalCiudad, l.LocalSector, COUNT(v.idTableVenta) AS Cantidad_Tickets
FROM TableLocal l
JOIN TableVenta v ON l.idTableLocal = v.TableLocal_idTableLocal
GROUP BY l.idTableLocal, l.LocalCiudad, l.LocalSector
ORDER BY Cantidad_Tickets DESC;8. Ventas por tipo de envase
¿La gente prefiere, Vaso o Cono? Esto ayuda a la gestión de compras (vasos de cartón vs galleta -cono-).
SELECT TipoEnvase, COUNT(*) AS Preferencia
FROM TableVenta
GROUP BY TipoEnvase
ORDER BY Preferencia DESC;9. El “Ticket Promedio” general de la cadena
¿Cuánto es el promedio de unidades de helado que un cliente se lleva por cada visita?
SELECT AVG(TotalUnidades) AS Promedio_Unidades_Por_Venta
FROM (
SELECT TableVenta_idTableVenta, SUM(Cantidad) AS TotalUnidades
FROM TableDetalleVentas
GROUP BY TableVenta_idTableVenta
) AS Subconsulta;(Nota técnica: Usamos una subconsulta para sumar las unidades por venta primero, y luego calcular el promedio global).
10. Análisis de ingresos: El mejor cliente
¿Qué cliente ha gastado más dinero en la heladería?
SELECT c.Nombre, SUM(d.Cantidad * s.PrecioUnitarioSabor) AS Dinero_Invertido
FROM TableClientes c
JOIN TableVenta v ON c.idTableClientes = v.TableClientes_idTableClientes
JOIN TableDetalleVentas d ON v.idTableVenta = d.TableVenta_idTableVenta
JOIN TableSabores s ON d.TableSabores_idTableSabores = s.idTableSabores
GROUP BY c.idTableClientes, c.Nombre
ORDER BY Dinero_Invertido DESC
LIMIT 1;↩️ index