LABS · JULIÁN LÓPEZ JIMÉNEZ · RECURSO GRATUITO

¿Por qué no cuadran las ventas?

Tres errores de un informe de tienda: contactos ausentes, totales duplicados y clientes sin compras. SQLite real en el navegador, guía y práctica local sin cuenta ni tarjeta.

Qué necesitas: Desde cero: lee primero el esquema y las pistas. JavaScript/WebAssembly y conexión inicial. Para repetir fuera: SQLite 3 o DB Browser; no necesitas Python.

Tu resultado: Tres consultas explicadas que conservan ausencias y cuentan ventas sin duplicarlas.

Prepara tu copia local

Crea una base vacía con SQLite 3 o DB Browser y ejecuta este SQL una vez. Guarda una copia original. Activa PRAGMA foreign_keys=ON en cada conexión. Las consultas posteriores se ejecutan sobre esta base ficticia.

PRAGMA foreign_keys=ON;
CREATE TABLE clientes(id INTEGER PRIMARY KEY,nombre TEXT NOT NULL,ciudad TEXT,email TEXT UNIQUE);
CREATE TABLE productos(id INTEGER PRIMARY KEY,nombre TEXT NOT NULL,categoria TEXT NOT NULL,precio_cent INTEGER NOT NULL CHECK(precio_cent>=0));
CREATE TABLE pedidos(id INTEGER PRIMARY KEY,cliente_id INTEGER NOT NULL REFERENCES clientes(id),fecha TEXT NOT NULL,estado TEXT NOT NULL CHECK(estado IN ('pagado','pendiente','cancelado')));
CREATE TABLE lineas(pedido_id INTEGER NOT NULL REFERENCES pedidos(id),producto_id INTEGER NOT NULL REFERENCES productos(id),cantidad INTEGER NOT NULL CHECK(cantidad>0),precio_cent INTEGER NOT NULL CHECK(precio_cent>=0),PRIMARY KEY(pedido_id,producto_id));
INSERT INTO clientes VALUES(1,'Ana','Sevilla','ana@example.invalid'),(2,'Bruno','Cádiz',NULL),(3,'Carla',NULL,'carla@example.invalid'),(4,'Diego','Sevilla','diego@example.invalid'),(5,'Elena','Cádiz',NULL);
INSERT INTO productos VALUES(1,'Teclado','perifericos',1500),(2,'Ratón','perifericos',2200),(3,'Manual','libros',900),(4,'Cable','cables',700),(5,'Pantalla','pantallas',25000);
INSERT INTO pedidos VALUES(10,1,'2026-01-03','pagado'),(11,1,'2026-01-20','pendiente'),(12,2,'2026-02-01','pagado'),(13,3,'2026-02-28','cancelado'),(14,2,'2026-03-01','pagado'),(15,4,'2026-02-10','pagado');
INSERT INTO lineas VALUES(10,1,2,1000),(10,2,1,2000),(11,3,2,900),(12,2,2,2000),(12,3,1,900),(13,1,1,1000),(14,4,3,700),(15,3,1,900);
Necesito una pista

No uses tus datos reales.

Ver solución de este reto
SELECT COUNT(*) AS clientes FROM clientes;

La base de ejemplo tiene cinco clientes; no todos tienen email ni pedidos.

Comprueba: Crea otra base vacía para repetir.

Reto 1 · Los contactos desaparecidos

NULL representa un valor desconocido o ausente. Compararlo con = produce desconocido, no verdadero; usa IS NULL o IS NOT NULL. Una cadena vacía es un valor distinto de NULL. El recurso gratuito retoma este problema en un informe de contacto. Devuelve id y nombre de clientes sin email, por id. Incluye NULL, no inventes correos.

-- Escribe tu SQL aquí.
Necesito una pista

La condición se refiere a email. → No uses = NULL. → WHERE email IS NULL selecciona ausencias.

Ver solución de este reto
SELECT id,nombre FROM clientes WHERE email IS NULL ORDER BY id;

No rellenar un dato ficticio conserva la diferencia entre desconocido y conocido.

Comprueba: Añade en una copia un cliente con email='' y compara ambos filtros.

Reto 2 · El total se ha duplicado

Cada pedido se repite por sus líneas al unir pedidos y lineas. Para contar pedidos usa COUNT(DISTINCT p.id); para facturar suma cada importe de línea. SUM(DISTINCT importe) no arregla duplicados: dos ventas distintas pueden costar lo mismo. Las variantes contienen importes iguales adrede. Devuelve una fila pedidos y total_cent de pedidos pagados; cuenta cada pedido una vez y suma todas sus líneas. Cero si no hay ventas.

-- Escribe tu SQL aquí.
Necesito una pista

El grano de la unión es una línea. → Cuenta DISTINCT p.id, pero no importes DISTINCT. → SUM(l.cantidad*l.precio_cent) conserva ventas distintas de igual precio.

Ver solución de este reto
SELECT COUNT(DISTINCT p.id) AS pedidos,COALESCE(SUM(l.cantidad*l.precio_cent),0) AS total_cent FROM pedidos p JOIN lineas l ON l.pedido_id=p.id WHERE p.estado='pagado';

COUNT(DISTINCT) corrige una identidad repetida; no es un permiso para eliminar importes que coinciden.

Comprueba: Compara COUNT(*) y COUNT(DISTINCT p.id). El contrato de esta base incluye al menos una línea por pedido cargado.

Reto 3 · Nadie se queda fuera

La consulta SELECT c.nombre,COUNT(*),SUM(DISTINCT l.precio_cent) mezcla personas con igual nombre, cuenta líneas como pedidos, pierde cantidades y elimina importes legítimos. El informe debe declarar población, grano, estado, unidades y orden. Corregir SQL empieza por acordar ese contrato. Devuelve id,nombre,pedidos,total_cent para todos los clientes. Solo ventas pagadas; incluye cero. Cuenta pedidos distintos, suma cantidad*precio_cent de líneas y ordena c.id.

-- Escribe tu SQL aquí.
Necesito una pista

Empieza por toda la población clientes. → El filtro de pagado pertenece al ON. → COUNT(DISTINCT p.id) y SUM de líneas responden a granos diferentes.

Ver solución de este reto
SELECT c.id,c.nombre,COUNT(DISTINCT p.id) AS pedidos,COALESCE(SUM(l.cantidad*l.precio_cent),0) AS total_cent FROM clientes c LEFT JOIN pedidos p ON p.cliente_id=c.id AND p.estado='pagado' LEFT JOIN lineas l ON l.pedido_id=p.id GROUP BY c.id,c.nombre ORDER BY c.id;

Agrupar por identidad conserva a dos personas del mismo nombre; el LEFT JOIN conserva a quien no compró.

Comprueba: Crea dos clientes llamados Ana; explica por qué no deben fusionarse.

Tu turno

Prepara un informe de contactos conocidos: id,email de clientes cuyo email no sea NULL, por id. Predice primero quién quedará fuera.

Ver solución y explicación
SELECT id,email FROM clientes WHERE email IS NOT NULL ORDER BY id;

IS NOT NULL conserva contactos conocidos. No sustituye los ausentes por direcciones inventadas. Una cadena vacía sigue siendo texto: prueba ese caso en una copia.

Explícalo con tus palabras

Repite con datos distintos antes de marcarlo como practicado. Esta es una autoevaluación, no una certificación.

Si quieres continuar

Aprende desde cero a consultar, relacionar y modificar copias con garantías; entrega un informe con evidencia.

Ver el curso De cero a SQL y su temario

Fuente: Recurso original de Labs. Puedes guardar este HTML, abrirlo sin conexión o imprimirlo como PDF desde tu navegador. No guarda tus respuestas ni el progreso de tu cuenta.