Detectar días consecutivos en SQL: una historia, un patrón y una solución práctica
Durante años he visto una escena repetirse en distintos espacios. Alguien publica un problema sobre “días consecutivos” y la conversación se enciende. A veces aparece en retos de acceso a sistemas, otras en análisis de inventario, o incluso en métricas de actividad diaria. Cada vez que encuentro el problema de “detectar días consecutivos en SQL”, recuerdo que este asunto es más común de lo que parece y que vale la pena explicarlo con calma.
Con esa idea en mente, preparé este ejemplo. Lo uso para mostrar cómo identifico rachas de actividad sin caer en trampas típicas, como duplicados o saltos de fecha que hacen que la racha no sea evidente.
Antes de entrar en materia, hago una aclaratoria: a diferencia de otros artículos donde incluyo la solución dentro del texto, en este caso no la publicaré aquí. Prefiero mantener el enfoque en el patrón y en la lógica que lo sostiene, y dejar el código como un recurso aparte, accesible mediante un enlace al final del artículo.
1. El problema real: detectar días consecutivos
En cualquier sistema donde se registran eventos por fecha, tarde o temprano surge la necesidad de encontrar rachas. Puede ser una secuencia de accesos, días con inventario bajo cierto umbral, o cualquier comportamiento que dependa de continuidad temporal.
El reto no está en obtener las fechas, sino en detectar dónde empieza y termina una racha. Ese es el corazón del problema.
2. El esquema mínimo de datos
Para ilustrar la idea, uso un modelo sencillo:
- un usuario (
usr) - una fecha (
log_date)
Esta combinación basta para explicar el patrón. Si en lugar de usuarios tuviera fabricantes, sensores o piezas de inventario, la lógica sería la misma. Lo importante es que exista un identificador y una fecha asociada.
En mi script, estos datos viven en cte_log. Allí incluyo duplicados a propósito, porque en la práctica aparecen con frecuencia y afectan el cálculo de rachas.
3. Entender el patrón: huecos, rachas y el truco del offset
Antes de escribir el código, reviso el comportamiento con un ejemplo pequeño. Me ayuda a ver la estructura del problema sin distracciones. Tomo un solo usuario y coloco varias fechas. Luego ordeno la secuencia y observo dónde se mantiene la continuidad o aparece un salto.
3.1 La tabla mínima necesaria
Para mostrarlo con claridad, preparo un ejemplo que contiene tres rachas:
- Racha A: 2026‑02‑01 → 2026‑02‑03
- Racha B: 2026‑02‑05 → 2026‑02‑06
- Racha C: 2026‑02‑10 → 2026‑02‑12
Esta es la tabla ordenada por fecha. Antes de seguir, aclaro que tanto row_number como offset son cálculos que aplico sobre los registros de cada usuario.
| log_date | row_number | offset = log_date − row_number |
|---|---|---|
| 2026‑02‑01 | 1 | 2026‑01‑31 |
| 2026‑02‑02 | 2 | 2026‑01‑31 |
| 2026‑02‑03 | 3 | 2026‑01‑31 |
| (hueco) | — | — |
| 2026‑02‑05 | 4 | 2026‑02‑01 |
| 2026‑02‑06 | 5 | 2026‑02‑01 |
| (hueco) | — | — |
| 2026‑02‑10 | 6 | 2026‑02‑04 |
| 2026‑02‑11 | 7 | 2026‑02‑04 |
| 2026‑02‑12 | 8 | 2026‑02‑04 |
3.2 El análisis
Al revisar la tabla, aparece un patrón claro: el valor del offset se mantiene cuando las fechas avanzan sin interrupciones. Este actúa como un ancla o inicio de cada racha:
- En la primera, el offset es 2026‑01‑31.
- La segunda lo lleva a 2026‑02‑01.
- Finalmente, la tercera lo deja en 2026‑02‑04.
Cuando el offset cambia, sé que apareció un hueco. Si se mantiene, la racha sigue su curso. A partir de ahí la conclusión es directa:
- Una racha comienza en la fecha más antigua de cada bloque con el mismo offset.
- Termina en la fecha más reciente del bloque.
Con esas dos fechas puedo estimar la duración de la racha. Para conocer cuántos días consecutivos hay, basta con contar las filas del bloque. El análisis me permite llegar al código con una idea clara: solo necesito reproducir este comportamiento con ROW_NUMBER() y un cálculo dentro de una CTE, un recurso que he usado en otras publicaciones.
4. Implementación por pasos en T‑SQL
Con el patrón claro, paso al código. Lo divido en etapas porque cada una resuelve un detalle específico.
4.1. Datos originales: cte_log
Aquí escribo los registros tal como podrían llegar desde un sistema real. Incluyo duplicados para indicar que conviene limpiarlos antes de calcular rachas. En este ejemplo la limpieza es sencilla, pero no siempre ocurre así. Si el problema fuera ‘encontrar los días consecutivos donde el inventario estuvo por debajo de cierto nivel’, primero tendría que filtrar los registros que realmente me sirven. De lo contrario, terminaría analizando fechas que no cumplen la condición y la racha perdería sentido. Esto es clave para que el cálculo refleje el comportamiento que quiero estudiar.
4.2. Limpieza: cte_distinct
Elimino duplicados por (usr, log_date). Este paso evita que una racha se infle por múltiples eventos en un mismo día. En problemas similares conviene hacer la limpieza necesaria antes de avanzar: filtrar los registros que realmente importan, ignorar los que no aportan al análisis, corregir nombres cuando vienen inconsistentes… en fin, dejar la tabla en un estado que permita estudiar la secuencia sin ruido. Ese trabajo previo marca la diferencia entre una racha bien calculada y un resultado engañoso.
4.3. Cálculo del offset: cte_window
Asigno un ROW_NUMBER() por usuario y lo ordeno por fecha. Después calculo el offset, que me permite distinguir cada racha. Ese valor se convierte en la referencia que uso para agrupar las fechas consecutivas.
4.4. Consulta final
Agrupo por usuario y por el valor del offset (streak_start). A partir de ahí obtengo la fecha inicial de cada racha y cuento cuántos días la forman. Luego aplico un filtro para quedarme solo con las rachas que alcanzan al menos tres días, aunque ese umbral puede cambiarse según lo que necesite analizar.
El script completo incluye comentarios que explican cada paso y el aviso legal bilingüe que uso en mis ejemplos.
Conclusiones
Este patrón me ha acompañado durante mucho tiempo. Lo uso porque es claro, estable y funciona bien incluso cuando los datos vienen con ruido. Una vez que entiendo cómo se comportan las fechas en una tabla pequeña, el resto fluye con naturalidad.
La lógica se resume en cuatro ideas:
- ordenar por fecha
- asignar un número de fila
- calcular el offset
- agrupar por ese valor
Con este enfoque puedo detectar rachas en múltiples contextos: accesos, inventario, sensores, actividad de usuarios o cualquier proceso que dependa de continuidad temporal.
Si quieres adaptar este ejemplo a tus propios datos, basta con seguir el flujo que usé en el artículo.
Foto de Atlantic Ambience – pexels.com



Pingback: Rachas en SQL - sqlenvivo.com