Pensábamos que lo peor ya había pasado. Que después de clasificar refugios, contar infectados y trazar mapas de avistamientos podríamos respirar. Ilusos.
La tercera y última parte nos obligó a sacar toda la artillería. Ya no bastaba con consultar datos. Había que analizar patrones, construir rutas de evacuación dinámicas y generar informes multidimensionales. Y sí, todo eso con SQL.
Aquí tienes las soluciones explicadas de los últimos cinco retos. Si estás leyendo esto, es que todavía no te han comido.

Reto 3.1 – Ranking por armamento: quién manda aquí
La cosa está cada vez peor, necesitamos urgentemente asignar un número a cada refugio según la cantidad de armas que tiene. Así de simple. O así de esencial, si estás organizando una defensa coordinada y necesitas saber a quién se le puede confiar una ametralladora sin que se dispare en el pie.
SELECT RefugeID, Weapons,
ROW_NUMBER() OVER (ORDER BY Weapons DESC) AS WeaponsRank
FROM RefugeSupplies
ORDER BY WeaponsRank;
Empezamos suavecito, hay que calentar. ROW_NUMBER() es una función de ventana que genera un contador dentro del conjunto de datos, según el orden que tú le digas. Aquí lo ordenamos por Weapons DESC, es decir, del más armado al menos. Cada refugio recibe un número único, sin importar si hay empates.
Esto no devuelve “quién tiene más armas”, sino quién va primero, segundo, tercero…. Una forma de tomar decisiones rápidas sin tener que pensar demasiado. Como debe ser en medio de un asalto mutante.
Reto 3.2 – Comparar infectados entre refugios vecinos
Se nos pide una labor fundamental para analizar la situación, ver si los números de infectados suben o bajan en los refugios contiguos. No para hacer turismo sanitario, sino para prever si un brote se está expandiendo.
SELECT RefugeID, Infected,
LAG(Infected, 1) OVER (ORDER BY RefugeID) AS PrevInfected,
LEAD(Infected, 1) OVER (ORDER BY RefugeID) AS NextInfected
FROM SurvivorStats
ORDER BY RefugeID;
Y ahora si, funciones de ventana de verdad, LAG(Infected, 1) devuelve el número de infectados del refugio anterior mientras que LEAD(Infected, 1) devuelve el número de infectados del siguiente. Ambos requieren un orden: en este caso, ORDER BY RefugeID.
Esta es una forma elegante de comparar filas sin tener que auto-unir la tabla consigo misma. Ideal para ver tendencias, anomalías… o refugios que están a punto de convertirse en un problema.
Reto 3.3 – Avistamientos y totales con ROLLUP
Otro imprescindible, crear un informe con los avistamientos de mutantes por día y ubicación, pero incluyendo totales parciales. ¿Por qué? Porque en el apocalipsis, igual que en el día a día en la oficina, alguien en la cadena de mando pidió “una vista agregada para facilitar la toma de decisiones” y no supimos decir que no.
SELECT CAST(SightingDate AS DATE) AS SightDate, Latitude, Longitude, COUNT(*) AS SightingsCount
FROM MutantSightings
GROUP BY ROLLUP (CAST(SightingDate AS DATE), Latitude, Longitude)
ORDER BY SightDate, Latitude, Longitude;
En este caso ROLLUP nos permite agrupar en varios niveles:
- Día + coordenadas → número de avistamientos
- Día (sin coordenadas) → total de ese día
- Total global
Esto genera filas con valores NULL en las columnas que se van agregando. Si no sabes leer esas NULL, no estás leyendo totales. Estás leyendo confusión. Lo bueno: es más limpio que hacer varias consultas. Lo malo: requiere que el que lo lea sepa lo que está viendo. No apto para jefes con prisas.
Reto 3.4 – Rutas de evacuación dinámicas con CTE recursiva
Entre los datos de infectados y los avistamientos nos empezamos a preocupar, ¿y si salir corriendo es la mejor opción? Por si acaso vamos a construir todas las rutas de evacuación posibles a partir del refugio 1, siguiendo las conexiones que tenemos en la tabla EvacuationRoutes.
WITH RoutesCTE AS (
SELECT FromRefugeID, ToRefugeID,
CAST(CONVERT(VARCHAR(MAX), FromRefugeID) + ' -> ' + CONVERT(VARCHAR(MAX), ToRefugeID) AS VARCHAR(MAX)) AS Path
FROM EvacuationRoutes
WHERE FromRefugeID = 1
UNION ALL
SELECT r.FromRefugeID, r.ToRefugeID,
CAST(c.Path + ' -> ' + CONVERT(VARCHAR(MAX), r.ToRefugeID) AS VARCHAR(MAX))
FROM EvacuationRoutes r
INNER JOIN RoutesCTE c ON r.FromRefugeID = c.ToRefugeID
)
SELECT Path
FROM RoutesCTE;
Esto es una CTE recursiva de manual. Traducido: una tabla temporal que se llama a sí misma para recorrer un camino paso a paso. En la parte “ancla” seleccionamos las rutas que salen del refugio 1 mientras que en la parte recursiva vamos empalmando los destinos como si siguiéramos el hilo de Ariadna, construyendo la ruta completa en texto (Path).
CAST y CONVERT se usan aquí para concatenar el camino en una cadena legible: 1 -> 2 -> 3 -> 4 -> 5.
¿Tiene límites? Claro. Sin control de ciclos puede acabar en bucle infinito, como los correos entre departamentos. Pero para rutas simples, esto es perfecto.
Reto 3.5 – Informes multidimensionales con CUBE
Por si acaso antes de irnos vamos a sacar un último informe de situación. Nos han pedido sacar un informe de cuántos refugios hay por estado (CRITICAL u OK) y por nivel de armamento, incluyendo subtotales y totales. Porque si, a alguien le ha parecido bien hacer una tabla dinámica sin usar Excel.
SELECT
CASE
WHEN FoodRations < 10 OR WaterLiters < 50 THEN 'CRITICAL'
ELSE 'OK'
END AS Status,
CASE
WHEN Weapons <= 20 THEN '0-20'
WHEN Weapons <= 50 THEN '21-50'
ELSE '>50'
END AS WeaponsGroup,
COUNT(*) AS RefugeCount
FROM RefugeSupplies
GROUP BY
CUBE (
CASE
WHEN FoodRations < 10 OR WaterLiters < 50 THEN 'CRITICAL'
ELSE 'OK'
END,
CASE
WHEN Weapons <= 20 THEN '0-20'
WHEN Weapons <= 50 THEN '21-50'
ELSE '>50'
END
)
ORDER BY Status, WeaponsGroup;
Esto tiene miga, lo sé. Vamos por partes. Primero agrupamos por dos variables derivadas con CASE estado (CRITICAL o OK) y rango de armas (0–20, 21–50, >50). Después, con CUBE generamos todas las combinaciones posibles:
- Cada grupo individual
- Totales por estado
- Totales por grupo de armas
- Total general (cuando ambas columnas son NULL)
Si ROLLUP ya era potente, CUBE es una navaja suiza para informes complejos. Útil, pero peligroso si no sabes leer lo que devuelve.
Conclusión
Estas consultas no se escriben con prisas. Se escriben con estrategia. En esta última fase, SQL dejó de ser una herramienta de lectura para convertirse en un lenguaje de decisión.
Desde rutas de evacuación recursivas hasta informes multidimensionales, estas técnicas separan a los que saben ejecutar un SELECT de los que pueden liderar una operación de supervivencia basada en datos.
¿Es el final? Por ahora. La amenaza mutante ha sido contenida. Pero si algo hemos aprendido de los datos… es que siempre vuelven.Y esta vez, estaremos listos.

