lección 6
Dimensiones que cambian: consultar el pasado sin falsearlo
Los clientes se mudan, los productos cambian de precio. Aprende a identificar tablas con historial y a consultarlas sin atribuir la venta de enero a la ciudad de julio.
⏱ 50 min
Ana vivía en Madrid cuando compró una bicicleta en enero. En marzo se mudó a Barcelona. Si hoy consultas "ventas por ciudad" y la dimensión de cliente dice que Ana vive en Barcelona, vas a atribuir esa venta de enero a Barcelona. El dinero entró en la tienda de Madrid, el equipo de Madrid la gestionó, pero tu informe dice que fue en Barcelona. La directora de la tienda de Madrid mira el informe y dice: "aquí falta una venta que yo sé que hicimos". Y tiene razón.
Este problema tiene un nombre técnico: Slowly Changing Dimensions (SCD), dimensiones que cambian lentamente. No es que cambien todo el rato — un cliente no se muda cada semana. Pero cuando cambian, el almacén tiene que decidir qué hacer con el pasado. Y esa decisión afecta directamente a cómo tú consultas la tabla. Si no sabes qué tipo de historial tiene tu dimensión, puedes estar falseando el pasado sin darte cuenta.
### Las tres formas de manejar el cambio
Cuando una dimensión cambia (un cliente se muda, un producto cambia de precio, un empleado cambia de departamento), el equipo de ingeniería elige una de tres estrategias. Cada una tiene implicaciones directas para ti como analista. No las diseñas, pero necesitas reconocer cuál está usando tu almacén para no cometer errores:
- 01.Tipo 1 — Sobrescribir: se machaca el valor antiguo con el nuevo. Ana decía "Madrid", ahora dice "Barcelona". El pasado desaparece. No hay forma de saber que antes vivía en Madrid. Todas las ventas históricas se ven con la ciudad ACTUAL.
- 02.Tipo 2 — Guardar historial: se crea una NUEVA fila para Ana-en-Barcelona, y la fila de Ana-en-Madrid se marca como "ya no vigente". La tabla tiene dos filas para Ana, cada una con un rango de fechas (valida_desde / valida_hasta). La versión que sigue vigente lleva una fecha de fin imposible, casi siempre 9999-12-31: es la forma de decir "esta todavía vale" sin dejar la columna vacía, y así los filtros por rango de fechas funcionan sin tener que tratar los nulos aparte. Las ventas de enero apuntan a Ana-Madrid y las de abril a Ana-Barcelona.
- 03.Tipo 3 — Columna adicional: se añade una columna "ciudad_anterior" junto a "ciudad_actual". Guarda un nivel de historial pero no más. Rara vez se usa en la práctica.
### Cómo te afecta el Tipo 2 al consultar
El Tipo 2 es el más común en almacenes bien construidos. Y tiene una implicación directa para ti: la tabla de dimensión tiene MÁS FILAS de las que esperarías. Si hay 50.000 clientes pero 20.000 se han mudado al menos una vez, dim_cliente tendrá 70.000 filas. Dos filas para el mismo cliente. Si haces un JOIN ingenuo contra el hecho, ¿con cuál de las dos filas se une cada venta?
La respuesta es: depende de cómo se construyó el almacén. En la versión bien hecha, la tabla de hechos apunta a la surrogate key de la VERSIÓN que era vigente en el momento de la venta. La venta de enero de Ana apunta a key=42 (Ana-Madrid), y la venta de abril apunta a key=99 (Ana-Barcelona). Tu JOIN funciona correctamente sin que tengas que hacer nada especial — el ingeniero de datos ya resolvió el problema al cargar los datos.
¿Y cómo sabes si el tuyo es de los bien hechos? Se comprueba en un minuto: cada venta tiene una fecha, y la versión con la que se une tiene un rango de vigencia. Si la carga está bien, la fecha de la venta cae dentro del rango. Si sale alguna fila donde no cae, es que el hecho apunta a la versión equivocada — casi siempre a la vigente hoy en vez de a la de entonces — y todos tus informes históricos están atribuidos al presente sin avisar.
PERO hay situaciones donde necesitas consultar la dimensión directamente (no a través del hecho). Por ejemplo: "dame la lista de clientes VIP actuales" o "¿cuántos clientes tenemos hoy en Barcelona?". En esos casos, necesitas filtrar por la versión VIGENTE. Si no filtras, cuentas las versiones históricas como si fueran clientes distintos.
1-- Tabla dim_cliente con SCD Tipo 2:2-- customer_key (PK, única por VERSIÓN)3-- customer_id (la misma persona tiene el mismo customer_id en todas sus versiones)4-- nombre, ciudad, segmento5-- valida_desde, valida_hasta, es_vigente67-- QUERY 1: "¿Cuántos clientes hay en Barcelona HOY?"8-- Necesitas filtrar por la versión vigente:9SELECT COUNT(*) AS clientes_bcn_hoy10FROM dim_cliente11WHERE ciudad = 'Barcelona'12 AND es_vigente = true;13-- Sin el filtro es_vigente, contarías también a quienes VIVIERON14-- en Barcelona pero ya se mudaron a otro sitio.1516-- QUERY 2: "Ventas por ciudad donde vivía el cliente en ese momento"17-- Aquí el JOIN normal funciona porque el hecho apunta a la versión correcta:18SELECT c.ciudad, SUM(f.importe) AS ventas19FROM fact_ventas f20JOIN dim_cliente c ON f.customer_key = c.customer_key21GROUP BY c.ciudad;22-- Cada venta apunta a la versión vigente en el momento de la compra.23-- Ana-enero → Madrid. Ana-abril → Barcelona. Correcto.2425-- QUERY 3: "¿La carga apunta a la versión correcta?"26-- Cada venta debería caer dentro del rango de vigencia de su versión.27SELECT COUNT(*) AS ventas_mal_atribuidas28FROM fact_ventas f29JOIN dim_cliente c ON f.customer_key = c.customer_key30WHERE f.fecha_venta < c.valida_desde31 OR f.fecha_venta > c.valida_hasta;32-- Si devuelve 0, la carga es correcta. Si devuelve algo, avisa a ingeniería.
El JOIN por customer_key respeta el historial. Pero si consultas dim_cliente sola, filtra por es_vigente.
Consejo de senior: la primera vez que consultes una dimensión con Tipo 2, haz esta comprobación: SELECT customer_id, COUNT(*) FROM dim_cliente GROUP BY customer_id HAVING COUNT(*) > 1. Si hay resultados, confirmas que hay múltiples versiones. Apunta cuántas filas "extra" hay — eso te dice el impacto potencial de un JOIN descuidado.
### Los peligros concretos para el analista
Hay tres errores típicos que cometes si no sabes que una dimensión es Tipo 2:
- 01.Contar clientes de más. Si haces COUNT(DISTINCT customer_key) en vez de COUNT(DISTINCT customer_id), cada cliente con historial cuenta 2 o 3 veces. Un almacén con 50.000 clientes reales te daría 70.000 "clientes" si no usas la columna correcta.
- 02.Multiplicar ventas en JOINs manuales. Si en vez de usar la FK del hecho haces JOIN por customer_id (la clave natural), un hecho con customer_id = 'ANA123' se une a TODAS las versiones de Ana, multiplicando la venta.
- 03.Atribuir al presente lo que era del pasado (en Tipo 1). Si tu dimensión es Tipo 1 y no lo sabes, todas las ventas históricas se ven con los datos ACTUALES del cliente. El informe dice "Barcelona vendió X" pero parte de ese X se generó cuando el cliente vivía en Madrid.
El error más silencioso: en dimensiones Tipo 1, NO HAY FORMA de saber que el dato ha cambiado. Si tu almacén usa Tipo 1 para la ciudad del cliente y Ana se mudó, el pasado ya está escrito con la nueva ciudad. No puedes corregirlo ni detectarlo. Es una limitación del almacén, no un error tuyo — pero necesitas saberla para no prometer "ventas por ciudad donde vivía el cliente EN ESE MOMENTO" si el almacén no guarda ese historial.
### Cómo identificar si una dimensión tiene historial
Cuando llegas a un almacén nuevo, estas son las pistas para detectar una dimensión Tipo 2:
- Columnas con nombres como: valida_desde, valida_hasta, valid_from, valid_to, effective_date, end_date
- Una columna booleana: es_vigente, is_current, is_active
- Más filas de las esperadas: si sabes que hay 50.000 clientes pero la tabla tiene 70.000 filas
- Dos columnas de clave: una surrogate (customer_key, autoincremental) y una natural (customer_id, la del sistema fuente)
- La FK del hecho apunta a la surrogate key, no a la natural key
### Patrón de consulta: ventas por ciudad histórica
El patrón correcto para "ventas por ciudad donde vivía el cliente en el momento de la compra" es el JOIN directo por la surrogate key. Es el patrón normal que ya conoces. La magia la hizo el equipo de ingeniería al cargar los datos: cada venta apunta a la versión correcta de la dimensión. Tú solo haces el JOIN y obtienes la respuesta históricamente correcta.
Donde se complica es si necesitas una pregunta que mezcla presente y pasado. Por ejemplo: "de los clientes que HOY viven en Barcelona, ¿cuánto compraron ANTES de mudarse?". Ahí necesitas cruzar la versión vigente (para filtrar por Barcelona actual) con las versiones históricas (para incluir sus compras cuando vivían en otro sitio). Es una consulta más avanzada, pero el patrón es reconocible:
1-- "Ventas totales de clientes que HOY viven en Barcelona,2-- incluyendo lo que compraron cuando vivían en otra ciudad"34-- Paso 1: encontrar los customer_id que HOY viven en Barcelona5-- Paso 2: buscar TODAS sus versiones (todas sus customer_key)6-- Paso 3: sumar las ventas de todas esas keys78SELECT SUM(f.importe) AS ventas_totales9FROM fact_ventas f10JOIN dim_cliente c ON f.customer_key = c.customer_key11WHERE c.customer_id IN (12 -- Clientes que HOY viven en Barcelona13 SELECT customer_id14 FROM dim_cliente15 WHERE ciudad = 'Barcelona' AND es_vigente = true16);1718-- Nota: el JOIN une con TODAS las versiones de esos clientes,19-- así que incluye ventas de cuando vivían en Madrid, Sevilla, etc.20-- Eso es lo que pide la pregunta.
Cuando necesitas mezclar presente (filtro) y pasado (ventas), usas customer_id en el WHERE y customer_key en el JOIN.
Consejo de senior: si tu almacén es Tipo 1 y te piden un análisis históricamente correcto, la respuesta honesta es "no puedo". No inventes datos que no existen. Explica que el almacén no guarda el historial de esa dimensión y que lo que puedes dar es "ventas atribuidas a la ciudad ACTUAL del cliente, no a la que tenía en el momento de la compra". Es mejor un análisis con una limitación explícita que uno con una respuesta silenciosamente falsa.
### Resumen: lo que cambió en tu forma de consultar
- 01.Averigua si tus dimensiones son Tipo 1 o Tipo 2. Busca columnas de vigencia (valida_desde, es_vigente).
- 02.Si es Tipo 2, para ventas históricas usa la FK del hecho (customer_key). El historial ya está resuelto.
- 03.Si consultas la dimensión directamente, filtra por es_vigente = true para evitar contar versiones múltiples.
- 04.Para contar PERSONAS, usa COUNT(DISTINCT customer_id), nunca COUNT(DISTINCT customer_key).
- 05.Si es Tipo 1, asume que el pasado refleja el PRESENTE, no el momento real. No puedes arreglarlo.
- 06.Cuando mezcles presente y pasado, filtra por customer_id (la persona) y une por customer_key (la versión).
Con esta lección cierras el ciclo del modelo de datos: sabes de dónde consultar (OLAP), qué tipos de tablas hay (hechos y dimensiones), cómo se organizan (estrella), qué es cada fila (grano), cómo agregar cada métrica (aditiva o no), y cómo manejar dimensiones que cambian. Este conocimiento es el mapa que necesitas para trabajar con cualquier almacén de datos que te pongan delante. Lo que viene ahora es aplicar todo esto a un almacén real y descubrir los trucos prácticos que separan la teoría de la realidad.
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...