Una consulta puede ejecutarse sin errores y producir un total equivocado. Si unes ventas con sus líneas de detalle, cada venta puede aparecer varias veces. Después, un SUM del importe original suma esas repeticiones.
En este tutorial vas a reproducir el fallo, localizarlo y corregirlo. También tienes un prompt para pedir ayuda a una IA sin saltarte la parte importante: entender qué representa cada fila y comprobar los resultados por tu cuenta.
1. Prepara tres ventas y calcula el resultado a mano
Trabaja en una base SQLite nueva y vacía, nunca en producción. Todos los nombres e importes son ficticios. Guarda los importes en céntimos enteros para que este ejercicio sea fácil de comprobar.
La tabla ventas tiene una fila por operación. La tabla lineas registra sus productos. Una venta puede tener varias líneas; dejamos otra sin detalle cargado para comprobar que no desaparezca del informe.
PRAGMA foreign_keys = ON;
CREATE TABLE ventas (
venta_id INTEGER PRIMARY KEY,
cliente TEXT NOT NULL,
total_centimos INTEGER NOT NULL
);
CREATE TABLE lineas (
linea_id INTEGER PRIMARY KEY,
venta_id INTEGER NOT NULL REFERENCES ventas(venta_id),
producto TEXT NOT NULL
);
INSERT INTO ventas VALUES
(1, 'Ana', 10000),
(2, 'Ana', 10000),
(3, 'Beto', 5000);
INSERT INTO lineas VALUES
(101, 1, 'Cuaderno'),
(102, 1, 'Lapiz'),
(103, 2, 'Agenda');
Antes de seguir, apunta tu predicción: tres ventas, 25.000 céntimos y tres líneas. Ana suma 20.000 céntimos en dos ventas; Beto, 5.000 en una. Compartir importe no convierte dos ventas en la misma operación.
Comprueba esa referencia directamente sobre ventas:
SELECT COUNT(*) AS ventas, SUM(total_centimos) AS total_centimos
FROM ventas;
Resultado: ventas = 3; total_centimos = 25000.
2. Ejecuta el JOIN que infla el total
Esta consulta es válida, pero calcula mal los ingresos:
SELECT COUNT(*) AS filas, SUM(v.total_centimos) AS total_centimos
FROM ventas AS v
LEFT JOIN lineas AS l ON l.venta_id = v.venta_id;
Devuelve filas = 4 y total_centimos = 35000. La venta 1 encuentra dos líneas: sus 10.000 céntimos aparecen dos veces. La venta 2 aparece una vez y la venta 3 se conserva sin detalle. El cálculo acaba siendo 10.000 + 10.000 + 10.000 + 5.000.
El cambio está en la unidad de cada fila, también llamada granularidad. Al combinar las tablas, ya no tienes necesariamente una fila por venta. La documentación de SELECT y JOIN en SQLite explica cómo se forman las coincidencias y cómo LEFT JOIN conserva las filas sin correspondencia.
Inspecciona la multiplicación por identificador:
SELECT v.venta_id,
COUNT(*) AS filas_tras_join,
COUNT(l.linea_id) AS lineas_reales
FROM ventas AS v
LEFT JOIN lineas AS l ON l.venta_id = v.venta_id
GROUP BY v.venta_id
ORDER BY v.venta_id;
Obtendrás estas ternas: (1, 2, 2), (2, 1, 1) y (3, 1, 0). La última resulta especialmente útil: existe una fila conservada por el JOIN, pero ninguna línea real. COUNT(columna) ignora los valores NULL; COUNT(*) cuenta filas, como recoge la documentación de agregados de SQLite.
3. Pide un diagnóstico que te obligue a aprender
Copia este prompt y añade los bloques anteriores, incluidos los datos:
Estoy aprendiendo SQL con SQLite. Actúa como tutor.
Con estas tablas espero 3 ventas y 25000 céntimos:
Ana: 2 ventas y 20000; Beto: 1 venta y 5000.
Mi LEFT JOIN devuelve 35000.
Primero, pídeme explicar qué representa una fila en cada tabla
y predecir cuántas filas tendrá cada venta después del JOIN.
No me des todavía la consulta corregida.
Después de mi respuesta, señala dónde se multiplica el importe.
Ayúdame a conservar ventas sin líneas y a obtener, por cliente,
el número de ventas, sus ingresos y el número de líneas.
Explica por qué SUM(DISTINCT importe) puede fallar.
Pídeme una prueba independiente y un caso límite adicional.
Usa solo el esquema proporcionado. Si falta una regla de negocio,
pregunta antes de inventarla. Distingue resultados calculados
mentalmente de consultas que realmente hayas ejecutado.
Usa datos sintéticos al practicar. Una respuesta convincente sigue necesitando una ejecución y una comparación con tu referencia. Este prompt es una propuesta didáctica; no representa una prueba comparativa de modelos de IA.
4. Evita el parche SUM(DISTINCT)
Prueba este atajo para ver por qué engaña:
SELECT SUM(DISTINCT v.total_centimos) AS total_centimos
FROM ventas AS v
LEFT JOIN lineas AS l ON l.venta_id = v.venta_id;
El resultado es 15.000. DISTINCT elimina importes repetidos antes de sumarlos, así que conserva únicamente 10.000 y 5.000. Ha eliminado también una venta legítima de Ana. Lo documentado para DISTINCT dentro de agregados es la eliminación de valores duplicados, no la identificación de operaciones.
5. Agrupa el detalle antes de unirlo
Si el informe necesita ingresos y número de líneas, resume primero las líneas a una fila por venta:
WITH lineas_por_venta AS (
SELECT venta_id, COUNT(*) AS n_lineas
FROM lineas
GROUP BY venta_id
)
SELECT v.cliente,
COUNT(*) AS ventas,
SUM(v.total_centimos) AS total_centimos,
SUM(COALESCE(l.n_lineas, 0)) AS lineas
FROM ventas AS v
LEFT JOIN lineas_por_venta AS l ON l.venta_id = v.venta_id
GROUP BY v.cliente
ORDER BY v.cliente;
Resultado esperado y ejecutado:
- Ana: 2 ventas, 20000 céntimos, 3 líneas
- Beto: 1 venta, 5000 céntimos, 0 líneas
WITH da nombre al resultado intermedio mediante una CTE. Su alcance es esta sentencia, según la documentación de WITH. Aquí, GROUP BY garantiza una fila por venta en ese resumen. LEFT JOIN mantiene a Beto y COALESCE convierte su ausencia de líneas en cero.
Si únicamente necesitas ingresos por cliente, elimina la unión: la tabla ventas ya contiene lo necesario. Esa consulta sencilla sirve además como comprobación independiente:
SELECT cliente, COUNT(*) AS ventas,
SUM(total_centimos) AS total_centimos
FROM ventas
GROUP BY cliente
ORDER BY cliente;
Debe coincidir con las tres primeras columnas del informe corregido.
6. Comprueba que la solución resiste cambios
En tu base de práctica, añade otra línea a la primera venta:
INSERT INTO lineas VALUES (104, 1, 'Regla');
Repite ambas consultas. La correcta mantiene 25.000 céntimos y tres ventas; ahora cuenta cuatro líneas. La incorrecta sube a 45.000 céntimos. Puedes explicar el error sin depender de la respuesta de la IA.
Los bloques se ejecutaron el 30 de septiembre de 2026 con SQLite 3.53.1 mediante Python 3.12.14, en una base en memoria, sin conexión a una base externa. Se verificaron los resultados iniciales y esta modificación. PRAGMA es específico de SQLite; para otros motores, adapta la configuración y comprueba el dialecto. No se ejecutaron pruebas en PostgreSQL o MySQL.
Continúa practicando SQL
Antes de confiar en un informe, define qué cuenta cada fila, calcula una referencia y prueba importes iguales y detalles ausentes. Si necesitas reforzar SELECT, filtros y agrupaciones, revisa el temario público de SQL para Principiantes en Cursalo y decide si encaja con tu punto de partida.