Saltar al contenido

lección 4

Granularidad: qué representa cada fila y por qué importa

Cada tabla tiene un grano: una fila = un pedido, o un día+tienda, o un clic. Si no lo sabes, puedes contar de más o de menos sin darte cuenta.

45 min

¿Cuántos pedidos hubo ayer? Parece una pregunta sencilla. Abres la tabla de ventas, escribes COUNT(*) WHERE fecha = ayer, y obtienes 1.247. Le dices al director comercial: "1.247 pedidos". Y el director te mira con cara rara y dice: "eso no puede ser, el año pasado por estas fechas hacíamos 300 al día". Investigas, y descubres que cada pedido tiene varias líneas (un pedido con 4 productos son 4 filas). No había 1.247 pedidos — había 1.247 líneas de pedido, que son unas 310 ventas reales. El número que diste estaba mal un factor de 4x. Y la culpa no es del SQL: es que no sabías qué representaba cada fila.

Ese "qué representa cada fila" se llama granularidad (o grano). Es la definición más crítica de cualquier tabla de hechos, y como analista necesitas conocerla ANTES de escribir la primera query. No es un concepto de diseño que te quede lejos: es la diferencia entre un número correcto y uno que puede estar multiplicado por 2, por 5, o por 20 sin que te des cuenta.

### Qué es el grano de una tabla

El grano es la respuesta a la pregunta: "una fila de esta tabla, ¿qué es exactamente?". Puede ser una transacción individual (un clic, una compra, un envío), un resumen diario (ventas del día por tienda), o un snapshot periódico (saldo de cada cuenta al cierre de mes). El grano determina qué puedes contar con COUNT(*) y qué necesita un DISTINCT o un SUM más cuidadoso.

Piensa en una caja de zapatos llena de tiques de compra. Si cada tique es una compra completa (un cliente que pagó una vez), el grano es "una compra". Si cada tique es una línea de producto (el mismo recibo tiene tres líneas porque compraste tres cosas), el grano es "una línea de compra". En ambos casos la caja se ve igual desde fuera — pero COUNT(*) te da números completamente distintos, y solo uno de los dos responde a "¿cuántas compras hubo?".

  • Grano de transacción: una fila = un evento individual (un clic, un pedido, un envío)
  • Grano de línea: una fila = un producto dentro de un pedido (un pedido con 3 productos = 3 filas)
  • Grano diario: una fila = un resumen del día (ventas totales de una tienda en un día)
  • Grano de snapshot: una fila = el estado de algo en un momento (saldo de una cuenta al cierre de mes)
  • El mismo negocio puede tener tablas con distintos granos para distintas preguntas
COUNT(*) significa cosas distintas según el grano. Si no sabes el grano, no sabes qué estás contando.

### Cómo descubrir el grano de una tabla que no conoces

Llegas a un almacén nuevo y ves una tabla fact_ventas. ¿Una fila es un pedido completo o una línea de producto dentro del pedido? Hay tres formas de descubrirlo, ordenadas de mejor a peor:

  1. 01.Pregunta al equipo que la construyó. Si hay documentación o un data catalog (catálogo de datos), ahí debería estar la respuesta.
  2. 02.Mira la clave primaria o las columnas únicas. Si la PK es (pedido_id, producto_id), el grano es línea de pedido. Si la PK es solo pedido_id, el grano es pedido.
  3. 03.Explora los datos: mira si un mismo pedido_id aparece en varias filas. SELECT pedido_id, COUNT(*) FROM fact_ventas GROUP BY pedido_id HAVING COUNT(*) > 1. Si hay resultados, el grano es más fino que "pedido".
1-- Técnica para descubrir el grano: busca repeticiones
2-- Si pedido_id se repite, el grano NO es "un pedido por fila"
3
4SELECT pedido_id, COUNT(*) AS filas
5FROM fact_ventas
6GROUP BY pedido_id
7HAVING COUNT(*) > 1
8LIMIT 5;
9
10-- Si devuelve filas, como:
11-- pedido_id | filas
12-- 101 | 3
13-- 102 | 5
14-- Entonces cada pedido tiene varias filas → grano = línea de pedido
15
16-- Si no devuelve nada (0 filas), cada pedido aparece una sola vez
17-- → grano = pedido

Consulta de diagnóstico: descubre el grano mirando qué se repite.

Consejo de senior: el primer día en un almacén nuevo, lanzo esta query para cada tabla de hechos que veo. Tardo cinco minutos y me ahorro días de errores. Escribo los resultados en un post-it: "fact_ventas = una línea por producto+pedido. fact_visitas = un registro por sesión. fact_daily_kpis = un registro por día+tienda". Ese post-it es más útil que cualquier diagrama.

### Los errores que produce no conocer el grano

Cuando no conoces el grano, cometes dos tipos de error opuestos. El primero: contar de más. Haces COUNT(*) pensando que cuentas pedidos, pero cuentas líneas de pedido — y el número sale 3-4 veces mayor de lo real. El segundo: contar de menos. Haces COUNT(DISTINCT pedido_id) en una tabla cuyo grano ya es "un pedido por fila" — y pierdes rendimiento sin ganar nada, pero al menos el número sale bien.

El más peligroso es el primero. Un COUNT(*) que sale 4x mayor de lo esperado es obvio — alguien lo nota. Pero un SUM(importe) en una tabla de grano diario que en realidad ya es un total del día... devuelve el importe correcto sin que tengas que hacer nada especial. El peligro aparece si alguien recarga esa misma tabla a grano de transacción individual y tu query sigue igual: de repente el SUM sale 4x mayor, y tú no tocaste nada. El grano cambió bajo tus pies.

Error que he visto tres veces en empresas diferentes: alguien reporta "100.000 usuarios activos" cuando en realidad son 25.000 usuarios que generaron 100.000 sesiones. El COUNT(*) de la tabla de sesiones no son usuarios únicos — son sesiones. Cada usuario que entra 4 veces cuenta 4 veces en COUNT(*). Para usuarios únicos necesitas COUNT(DISTINCT user_id). El grano de la tabla es "sesión", no "usuario".

### Grano y GROUP BY: la relación directa

El grano determina qué puedes pedir a un GROUP BY sin perder información. Si tu tabla tiene grano diario por tienda (una fila = un día + una tienda), puedes agrupar por mes (sumar los días) o por región (sumar las tiendas). Pero NO puedes bajar al detalle de hora, ni ver producto individual — esa información no está en la tabla. El grano es el nivel más fino al que puedes llegar. Todo lo que sea más grueso es una agregación; todo lo que sea más fino no existe.

Esto te da una regla práctica: si alguien te pide un dato a un nivel más fino que el grano de tu tabla, necesitas OTRA tabla. Si tu tabla es diaria y te piden el desglose por hora, necesitas la tabla de transacciones individuales (que tiene el timestamp exacto). No puedes inventar el detalle que no existe con ningún truco de SQL.

Hay una cosa más que puede cambiarte el grano sin que nadie toque la tabla: un JOIN. La tabla sigue teniendo el suyo, el de siempre; lo que cambia es el grano de lo que estás mirando. Si cruzas tu tabla de hechos con una dimensión que tiene filas repetidas, las filas del hecho que casan con esas repeticiones salen varias veces, y lo que tienes delante ya no es "una fila por venta": es "una fila por venta y por cada copia de su producto". A eso se le llama el grano efectivo de la consulta. Ya sabes comprobarlo, porque es lo que hiciste con el esquema estrella: cuenta las filas antes y después del JOIN y mira si el número cambia. Si cambia, tu grano efectivo ya no es el de la tabla, y cualquier SUM que hagas encima está inflado.

Solo puedes subir desde el grano (agregar), nunca bajar (desglosar lo que no existe).

### Tablas con grano diario: cuando COUNT no es lo que esperas

Un caso especialmente tramposo: las tablas de KPIs diarios. Muchas empresas tienen una tabla tipo fact_daily_metrics con grano día+tienda donde cada fila ya tiene el total precalculado: total_ventas, num_pedidos, num_clientes_unicos. Si haces COUNT(*) GROUP BY mes en esa tabla, no cuentas pedidos — cuentas DÍAS. 30 filas por tienda al mes, multiplicado por el número de tiendas. El num_pedidos ya está EN la fila como columna; tu trabajo es hacer SUM(num_pedidos), no COUNT(*).

Y hay una columna que ni siquiera con SUM se arregla: la de clientes únicos. Si el lunes entraron 38 clientes distintos y el martes 42, sumarlos da 80 — pero el que entró los dos días se ha contado dos veces, y no hay forma de saber cuántos hicieron eso mirando solo esta tabla. Los totales diarios de una columna de únicos no se pueden sumar: para saber los clientes únicos del mes hay que volver a la tabla de transacciones individuales. Los ingresos y los pedidos sí se suman; los únicos, no. La lección siguiente va justo de esa diferencia.

1-- Tabla con grano DIARIO por tienda:
2-- Una fila = un día + una tienda
3-- Columnas: fecha, id_tienda, total_ventas, num_pedidos, clientes_unicos
4
5-- INCORRECTO: contar filas ≠ contar pedidos
6SELECT mes, COUNT(*) AS "esto NO son pedidos"
7FROM fact_daily_metrics
8GROUP BY mes;
9-- Resultado: 30 filas/tienda * 10 tiendas = 300 por mes. No son pedidos.
10
11-- CORRECTO: sumar la columna que ya tiene el total
12SELECT mes, SUM(num_pedidos) AS total_pedidos
13FROM fact_daily_metrics
14GROUP BY mes;
15-- Resultado: la suma real de pedidos de todas las tiendas en el mes.

En tablas de grano diario, los totales ya están en columnas. No hagas COUNT(*) — haz SUM de la columna correcta.

### Resumen: la checklist del grano

  1. 01.Antes de escribir la primera query, averigua el grano: "una fila de esta tabla = ¿qué?"
  2. 02.Si no lo sabes, usa GROUP BY + HAVING COUNT(*) > 1 para descubrirlo empíricamente.
  3. 03.COUNT(*) cuenta FILAS, no lo que tú crees. Si el grano es línea de pedido, COUNT(*) no son pedidos.
  4. 04.Para contar entidades únicas en tablas de grano fino, usa COUNT(DISTINCT columna).
  5. 05.En tablas de grano diario/mensual, los totales ya están precalculados en columnas. Usa SUM de esa columna.
  6. 06.Solo puedes agregar ARRIBA del grano (día → mes). No puedes desglosar por debajo (día → hora) si no tienes la hora.

Consejo de senior: cuando un número "no cuadra" y no encuentras el error en la lógica de la query, el 70% de las veces es un problema de grano. O estás contando filas en vez de entidades, o estás sumando algo que ya está sumado, o un JOIN ha multiplicado filas cambiando el grano efectivo. Vuelve siempre al básico: ¿qué es una fila aquí?

Ya sabes qué mide cada tabla (hechos vs dimensiones), cómo se conectan (esquema estrella) y qué representa cada fila (granularidad). Falta una pieza crucial: no todas las métricas se pueden agregar de la misma forma. En la siguiente lección vas a descubrir por qué sumar porcentajes da basura, y cómo distinguir lo que puedes sumar de lo que necesita un cálculo más cuidadoso.

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...