Teoría BBDD

SQL contra el Apocalipsis Mutante (Parte 5): Última defensa

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.

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.

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.

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.

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.

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.

Publicado por Roberto Carrancio en Cloud, SQL Server, 0 comentarios

SQL contra el Apocalipsis Mutante (Parte 4) La resistencia responde.

Cuando empezó el apocalipsis, todo era caos: refugios al límite, suministros escasos, datos sin contexto. Pero gracias a nuestras habilidades a base de SELECT, de JOIN, de WHERE y de horas frente al terminal, la resistencia se organizó.

En esta entrega, volvemos sobre los 10 retos iniciales (parte 1 y parte 2) para mostrar cómo lo resolvimos. No simplemente hablamos de respuestas frías, estamos hablando de pasos clave en la defensa de la humanidad. Porque cada consulta lanzada a la base de datos fue una decisión crítica, y cada decisión… salvó vidas.

Parte 1: Primeros pasos bajo presión

Empezaba la primera parte, sin mucha complicación, sin imaginarnos lo que después se iba a complicar. En estos primeros retos pudimos salir del paso con consultas sencillas que vamos a ver a continuación.

Reto 1.1 – Detectar refugios al borde del colapso

Tras semanas sin recibir suministros, varios refugios estaban al borde del colapso. Necesitábamos listar los refugios con menos de 10 raciones de comida o menos de 50 litros de agua. Teníamos que conocer el RefugeID, FoodRations y WaterLiters, ordenados de menor a mayor por FoodRations. 

Para resolver este reto necesitabamos ejecutar la consulta:

En este caso seleccionamos solo las columnas que necesitábamos, seleccionar más iría contra el rendimiento. Además, el WHERE nos filtra por las condiciones críticas que necesitamos y el ORDER BY pone en primer lugar a los que se están quedando sin comida. Porque el hambre puede causar más bajas que los mutantes. Los filtros se combinan con un OR porque con cumplir cualquiera de las dos condiciones el refugio está en riesgo de colapso.

Reto 1.2 – Localizar los mejor armados

Los refugios que hemos detectado antes necesitan ayuda y solo los mejor armados podrán proporcionarles. En este momento tenemos que localizar los 5 refugios con más armamento disponible.

En esta consulta, el TOP 5 combinado con el ORDER BY nos da los resultados deseados. Ordenamos descendente para tener primero los refugios con más armas y nos quedamos con los 5 primeros resultados.

Reto 1.3 – Delimitar la zona caliente

Nos informan de movimiento de mutantes entre las latitudes 39 y 41 y longitudes -75 y -73. Tenemos que localizar qué refugios están en esa zona.

Seleccionamos solo las columnas necesarias y usamos BETWEEN para filtrar por latitud y longitud. Al contrario que en el primer escenario, usamos un AND para combinar los filtros porque para estar en la zona de los mutantes los registros tienen que cumplir ambas condiciones (estar en la misma latitud y longitud).

Reto 1.4 – Cruzar población y recursos

Tener recursos está bien. Tener gente también. Pero si no cruzas esos datos, vuelas a ciegas. Esta unión entre tablas nos permitió ver la capacidad real de cada refugio: cuántas personas había y con qué contaban para resistir.

En este caso usamos INNER JOIN para unir las tablas RefugeSupplies y SurvivorStats usando el campo RefugeID que es común entre ellas en el ON. Sin esta consulta, no puedes tomar decisiones que impliquen vidas humanas.

Reto 1.5 – Refugios en riesgo inmediato

La cosa se ponía fea, teníamos que detectar que refugios tenían demasiada gente y pocas armas. Pero claro, esos datos para filtrar estaban en tablas distintas. Primero debíamos unirlas y después filtrar por los refugios que cumpliesen con las dos condiciones.

En este caso no hay nada nuevo, simplemente combinamos el INNER JOIN del reto anterior con filtros del WHERE que combinan las dos condiciones, muchas bocas, pocas balas. Si no se actuaba rápido, no quedaría nadie a quien alimentar.

Parte 2: Cuando los informes salvan vidas

La cosa se empieza a complicar, hasta ahora hemos leído datos tal como están en la base de datos pero no hemos operado con ellos. Si queremos salvar a la humanidad tenemos que ir un paso más allá.

Reto 2.1 – Calcular la tasa de infección

No basta con contar infectados. Hay que calcular su proporción. Este cálculo nos dará la tasa de infección por refugio, y necesitábamos saber los que superaban el 5%. Una columna más que números: un indicador de si la situación estaba bajo control… o fuera de él

Hay que hacer una división entre campos pero no es tan sencillo. El doble CAST es esencial, primero lo usamos para convertir a FLOAT para que la división no se redondee a entero y luego ya, el resultado multiplicado por 100 lo convertimos a DECIMAL(5,2) para obtener un porcentaje legible. Podríamos haberlo hecho también con CONVERT en vez de CAST siguiendo la misma lógica. 

Reto 2.2 – Clasificar automáticamente los refugios

El tiempo iba en nuestra contra y no podíamos revisar cada fila a mano. Necesitábamos etiquetar los refugios automáticamente. 

Usamos CASE para definir una lógica simple, si la comida o el agua está por debajo del mínimo, el refugio está en estado CRITICAL. Si no, está OK. Esta clasificación era la base de cualquier estrategia.

Reto 2.3 – ¿Cuántos están en cada estado?

Somos gente de datos, y de automatismos, no podemos estar contando cuántos refugios están bien y cuántos críticos. Tenemos que dar ese dato en la misma consulta.

Ya teníamos el estado individual de cada refugio. Nos basamos en la consulta anterior, quitamos las columnas que no nos interesan y usamos GROUP BY para agrupar por estado (CRITICAL u OK). Con eso y un COUNT(*) nos daba el número de refugios en cada grupo. 

Reto 2.4 – Avistamientos recientes por día

Las hordas no atacan a ciegas. Tampoco nosotros. Necesitábamos construir una consulta para seguir la evolución diaria de los avistamientos durante la última semana.

En este caso el CAST(… AS DATE) elimina la hora para agrupar correctamente por dia. Después, con DATEADD(…, -7, GETDATE()) calculábamos la fecha hace siete días. El resultado: una línea temporal del infierno.

Reto 2.5 – Amenazas cercanas a refugios vulnerables

Este fue el punto en que las cosas se pusieron serias de verdad. Necesitábamos una consulta que detectara avistamientos recientes cerca de los refugios más vulnerables.

Para ello crearemos una CTE con los refugios críticos y luego la consultaremos cruzando los datos con los de los avistamientos y las zonas.

Como decía primero creamos una CTE (CriticalRefuges) para aislar a los vulnerables. Luego, hacemos un JOIN con los avistamientos y filtramos:

  • Usamos ABS(…) < 0.5 para ver si la distancia (en coordenadas) entre refugio y avistamiento es menor de medio grado. ABS devuelve el valor absoluto (sin negativo), útil para comparar distancias.
  • También filtramos por fecha: solo avistamientos de los últimos 3 días.

Esta consulta era difícil. No tenemos un filtro de igualdad en el JOIN, lo que no es habitual. En su lugar tenemos los filtros con ABS que nos dan un cuadrado de 1 grado (0.5 arriba, abajo, izquierda y derecha) alrededor del refugio. En lugar de pedir que las coordenadas sean exactamente iguales, que sería muy improbable, buscamos avistamientos que estén dentro de una distancia tolerable.

¿Es correcto este JOIN sin filtro de igualdad? 

Si lo es. Mientras la condición del ON devuelva TRUE o FALSE para evaluar combinaciones de filas entre tablas, puedes usar cualquier lógica que tenga sentido: comparaciones, funciones, expresiones booleanas…

Eso sí, no es eficiente a gran escala. Si estás trabajando con millones de filas y distancias reales, lo suyo es usar funciones geoespaciales (GEOGRAPHY, STDistance, índices espaciales, etc.). Pero para nuestro contexto postapocalíptico con pocos refugios y unos pocos mutantes… sobra potencia.

En otras palabras, ese JOIN actúa como un filtro espacial aproximado, no como un emparejamiento exacto.

Conclusión

Estos diez retos no son simples ejercicios de SQL. Son decisiones técnicas con consecuencias narrativas y operativas. Cada uno nos enseñó algo: a leer mejor los datos, a cruzarlos con cabeza, a anticipar problemas. Pero si pensabas que eso era todo… no conoces el apocalipsis. Porque las consultas más complejas aún están por llegar. Y cuando lo hagan, necesitaremos algo más que SELECT. Nos vemos en la última entrega. Por si acaso trae casco. …O un bate con clavos.

Publicado por Roberto Carrancio en Cloud, SQL Server, 0 comentarios

SQL contra el Apocalipsis Mutante (Parte 3): El último asalto

Las noches son cada vez más largas. Los refugios que siguen en pie son fortalezas solitarias rodeadas de silencio y muerte. Pero los supervivientes que completaron las dos primeras partes de esta serie son distintos: dominan SELECTs afilados, JOINs letales y cálculos capaces de prever el caos. Han llegado hasta aquí por méritos propios.

Si acabas de llegar: detente. Aquí no hay atajos. Vuelve al primer capítulo y luego al segundo capítulo para convertirte en un verdadero experto en análisis de datos para sobrevivir al apocalipsis.

Hoy cruzaremos la última frontera: funciones de ventana, CTEs, ROLLUP y CUBE. Con estas habilidades, no solo resistirás: podrás planificar la evacuación, anticiparte a cada ataque y construir un futuro para los últimos humanos.

SurvivalDB: el campo de batalla definitivo

Nuestra base de datos SurvivalDB ha sido nuestro campo de entrenamiento y nuestra línea de defensa y en este último asalto no iba a ser menos. Sus tablas ya te son conocidas: RefugeSupplies, SurvivorStats, MutantSightings y EvacuationRoutes. Hoy, sacaremos todo el jugo posible de sus datos.

Ejercicios: la misión final para salvar a la humanidad

Después de una semana de resistencia, los refugios que aún aguantan necesitan más que coraje: requieren análisis avanzados para planificar el contragolpe o la evacuación definitiva. Hoy vamos a cruzar la última frontera de SQL: funciones de ventana que muestran lo que sucede a tu alrededor, CTEs que revelan rutas de huida imposibles y agregaciones que transforman el caos en información estratégica.

Cada consulta que planteamos a continuación no es sólo un ejercicio: es un paso esencial para decidir si la humanidad logra resistir un día más… o se convierte en recuerdo. Afilad vuestro teclado: el futuro se escribe en T-SQL.

Ejercicio 1: Prioriza refugios con funciones de ventana

Necesitamos saber el orden de prioridad para reforzar refugios: los que más armas tengan irán al final; primero hay que ayudar a los peores armados.

Tu misión: mostrar la información de los refugios con una columna WeaponsRank que indique la posición de cada refugio en el ranking por número de armas. 

Pista: cuando la prioridad depende del orden, las funciones de ventana son tu aliado.

Ejercicio 2: Compara infectados con LAG y LEAD

El Consejo de la Resistencia necesita saber si las infecciones están empeorando refugio a refugio. Comparar cada refugio con el anterior y el siguiente permitirá identificar focos crecientes.

Tu misión: mostrar RefugeID e Infected para cada refugio además de PrevInfected y NextInfected con los valores del refugio anterior y siguiente respectivamente.

Pista: dos funciones en la misma cláusula OVER te darán el pasado y el futuro.

Ejercicio 3: Totaliza avistamientos con ROLLUP

Los analistas necesitan saber la actividad mutante diaria y por zona, pero también los totales diarios y el gran total. Esta vista global decidirá si debemos evacuar o resistir.

Tu misión: agrupar avistamientos por fecha y coordenadas (Latitude, Longitude) y usa ROLLUP para incluir los totales parciales y el gran total.

Pista: agrupar con ROLLUP es como un seguro: te dará todos los niveles de resumen.

Ejercicio 4: Construye rutas de evacuación con CTE recursiva

La evacuación debe organizarse como una cadena desde el refugio inicial al último refugio seguro. Cada ruta conecta dos refugios; necesitamos todas las rutas posibles desde el refugio 1 para planificar el éxodo.

Tu misión: crear una CTE recursiva que muestre todas las rutas desde el refugio 1, listando la secuencia completa de refugios visitados como Path.

Pista: una CTE que se llame a sí misma traza caminos que una simple consulta jamás encontraría.

Ejercicio 5: Agrega por combinaciones con CUBE

Para decidir la distribución de suministros y armas, necesitamos un análisis de refugios agrupados por su estado (CRITICAL u OK) y por rango de armas (0-20, 21-50, >50). Los totales por cada combinación y los globales son imprescindibles.

Tu misión: agrupar por ambos criterios y usar CUBE para obtener todos los totales parciales y el gran total, mostrando también el número de refugios (RefugeCount).

Pista: CUBE permite ver cada combinación posible y cada subtotal en una sola consulta.

Consejo final: sobrevivir no es suerte, es optimización

Llegar hasta aquí no ha sido casualidad. Cada consulta ha reforzado tus habilidades y tu refugio. Pero como cualquier buen plan de supervivencia, hay que mantenerlo actualizado: los índices se fragmentan, los datos crecen y las hordas evolucionan. No bajes la guardia.

¿Quieres las respuestas?

El próximo martes tendremos en YouTube un vídeo con las soluciones paso a paso de este y los anteriores capítulos para que compruebes tu progreso y consolides tu aprendizaje. Pero si lo ves sin practicar antes… no esperes sobrevivir mucho tiempo. Trata de resolver estos ejercicios por tu cuenta antes de recurrir al vídeo y deja en comentarios tus respuestas.

El amanecer tras la noche más oscura

Han pasado varios días desde los primeros ataques. Gracias a los análisis, los refugios críticos fueron reforzados, los más armados ayudaron a los demás y las rutas de evacuación se activaron a tiempo. Los mutantes siguen ahí, pero los supervivientes también. Ahora dominan SQL como nadie y han aprendido que los datos, más que las balas, son el arma definitiva.

Mientras el sol asoma sobre un paisaje todavía plagado de ruinas, una última transmisión interfiere la radio de los refugios: un código desconocido que habla de nuevas hordas, más grandes, más rápidas… y de supervivientes que podrían estar organizándose más allá de lo que creíamos posible.

Porque en este apocalipsis, el verdadero final nunca llega. Y cuando vuelva la oscuridad, volveremos con nuevas queries, nuevas estrategias… y la misma sed de seguir vivos.

 

Publicado por Roberto Carrancio en Cloud, SQL Server, 1 comentario

SQL contra el Apocalipsis Mutante (Parte 2): Calcula antes de que te devoren

El humo aún se eleva sobre los refugios que visitamos en el primer capítulo. Algunos apenas aguantan con lo puesto; otros se preparan para resistir. Entre tablas, SELECTs y JOINs empezamos a entender quién tenía posibilidades y quién estaba condenado.

Si te acabas de unir a esta misión y todavía no has completado la Parte 1, detente: no hay gloria para los que intentan correr antes de aprender a caminar. Empieza aquí: SQL contra el Apocalipsis Mutante (Parte 1) y vuelve cuando hayas sobrevivido al primer asalto.

Para quienes seguís con nosotros: bienvenidos al siguiente nivel. Hoy dejaremos atrás los SELECT básicos y nos adentraremos en los cálculos, clasificaciones y agrupaciones. Porque en el apocalipsis, no basta con saber quién está mal; hace falta saber cuán mal están, agrupar por gravedad y priorizar qué hacer antes de que el caos sea total.

Recuerda el escenario: SurvivalDB

Trabajamos sobre la base de datos SurvivalDB, con estas tablas clave:

  • RefugeSupplies: inventario de suministros y armas de cada refugio.
  • SurvivorStats: población e infectados.
  • MutantSightings: registros de avistamientos mutantes.
  • EvacuationRoutes: rutas de evacuación posibles entre refugios.

Ejercicios: el análisis que separa la vida de la muerte

Lo fácil ya se ha acabado, hemos sobrevivido al primer dia pero aun queda mucho que hacer. Vamos a incrementar la dificultad y a trabajar con agrupaciones y operaciones matemáticas en SQL Server.

Ejercicio 1: Calcula la tasa de infección

A simple vista, los refugios parecen estar resistiendo, pero las infecciones avanzan como un cáncer. Queremos saber qué porcentaje de cada refugio está infectado, para encontrar los que superen el 5% y actuar antes de que el pánico los destruya por dentro.

Tu misión: calcula la tasa de infección de cada refugio con dos decimales, muestra RefugeID, Population, Infected y InfectionRate, y ordena por mayor porcentaje.

Pista: deberás hacer un cálculo sobre las columnas y filtrar con un porcentaje.

Ejercicio 2: Clasifica refugios según estado crítico

Los datos de suministros muestran diferencias brutales: algunos refugios están bien; otros al borde del abismo. Queremos etiquetar cada refugio como ‘CRITICAL’ u ‘OK’ según si tienen menos de 10 raciones de comida o menos de 50 litros de agua.

Tu misión: muestra RefugeID, FoodRations, WaterLiters y Status (la etiqueta calculada).

Pista: usa una expresión condicional para devolver un texto según la situación.

Ejercicio 3: Cuenta refugios por estado

Ahora que podemos etiquetar cada refugio, necesitamos saber cuántos están en estado ‘CRITICAL’ y cuántos en ‘OK’. Es el primer paso para dimensionar el desastre.

Tu misión: agrupa por el estado y cuenta el número de refugios en cada grupo como RefugeCount.

Pista: agrupa por la misma lógica que usaste para clasificar.

Ejercicio 4: Analiza actividad reciente de mutantes

Los avistamientos se suceden casi a diario. Necesitamos saber cuántos mutantes se han visto cada día en la última semana para anticipar si la actividad aumenta.

Tu misión: cuenta el número de avistamientos por día, mostrando la fecha (SightingDate como DATE) y el total.

Pista: filtra por fecha reciente, agrupa y cuenta.

Ejercicio 5: Mutantes cerca de refugios críticos

Los refugios en estado crítico son vulnerables. Si además tienen hordas mutantes cerca, la probabilidad de caída es altísima. Queremos combinar los refugios en ‘CRITICAL’ con los avistamientos de los últimos 3 días que estén a menos de 0,5 grados de sus coordenadas.

Tu misión: muestra RefugeID, SightingDate, Latitude, Longitude de los avistamientos cercanos.

Pista: une los datos de refugios críticos con los avistamientos recientes y aplica una condición de proximidad.

Consejo para cuando los mutantes golpeen la puerta

No olvides que los cálculos y agregaciones pueden ser tan peligrosos como un mutante si los usas mal: un mal GROUP BY puede hacer que creas que un refugio está a salvo cuando ya ha caído. Revísalos como revisarías las barricadas de tu refugio.

¿Quieres las respuestas?

Estoy preparando un vídeo en el canal de YouTube donde resolveremos paso a paso estos ejercicios y explicaremos cada detalle para que no sólo los copies, sino que los entiendas. Eso sí, tendrás que esperar a la semana que viene, mientras tanto intenta resolver estos ejercicios por tu cuenta para practicar y deja tus soluciones en comentarios.

Próximamente…

En la próxima entrega, el caos llegará al máximo nivel: funciones de ventana, CTEs y agregaciones avanzadas para planificar la evacuación definitiva. Porque el fin se acerca, y sólo los que dominan SQL hasta el último detalle podrán salir con vida.

Publicado por Roberto Carrancio en Cloud, SQL Server, 2 comentarios

SQL contra el Apocalipsis Mutante (Parte 1): Aprende SQL y salva el mundo

Las ciudades están en ruinas. Las carreteras, bloqueadas por coches abandonados. El olor a pólvora y carne podrida se mezcla en el aire mientras hordas de mutantes se extienden sin control. Los pocos refugios que aún se mantienen en pie dependen de un único recurso para resistir un día más: los datos de nuestra base de datos SurvivalDB. Y ahí entramos nosotros, con un SQL Server medio polvoriento pero funcionando como un reloj.

En este primer episodio de nuestra serie para aprender a analizar datos en SQL no vas a encontrar dashboards color pastel ni entornos de BI con animaciones, el mundo se acaba y no hay tiempo para eso. Solo tenemos datos crudos, queries eficientes y la urgencia de sobrevivir. Hoy vamos a plantear una serie de ejercicios básicos que te permitirán dominar SELECT, WHERE y JOIN, mientras ayudamos a los últimos supervivientes a organizar sus suministros, identificar amenazas y evitar convertirse en croquetas mutantes.

El escenario: SurvivalDB, el corazón de la resistencia

En nuestro mundo postapocalíptico, la base de datos SurvivalDB es el último bastión de la civilización. Contiene cuatro tablas esenciales que almacenan la información crítica para la resistencia:

  • RefugeSupplies: el inventario de cada refugio, incluyendo comida, agua, armas y ubicación geográfica (latitud y longitud). Saber qué refugio tiene recursos y cuál se muere de hambre es la diferencia entre enviar ayuda o enterrar cadáveres.
  • SurvivorStats: registra la población de cada refugio y el número de infectados. Porque nada genera más pánico que un refugio lleno de supervivientes a punto de convertirse en mutantes.
  • MutantSightings: almacena los avistamientos de hordas de mutantes con fecha y localización. Analizar estos datos permite anticipar ataques y planificar rutas seguras para los supervivientes.
  • EvacuationRoutes: describe las rutas de evacuación posibles entre refugios, indicando desde qué refugio se parte y a cuál se llega. Es vital para planificar la huida cuando la defensa ya no es una opción y sólo queda correr.

Te dejo el script para que crees tú mismo la base de datos, las tablas y los datos que vamos a usar en los ejercicios.

Ejercicios: cada consulta es un capítulo en la lucha por la supervivencia

El destino de los supervivientes depende de cómo consultemos estos datos. Un SELECT rápido puede significar reforzar un refugio a tiempo; un SELECT lento puede significar que ya es demasiado tarde. Comete cualquier error en tus consultas y podrías enviar suministros al refugio equivocado o ignorar un refugio al borde del colapso.

Ejercicio 1: Localiza los refugios al borde del colapso

Tras semanas sin recibir suministros, varios refugios podrían quedarse sin comida ni agua en cualquier momento. Si caen estos refugios, los supervivientes saldrán huyendo y propagarán el caos… o algo peor.

Tu misión: listar los refugios con menos de 10 raciones de comida o menos de 50 litros de agua. Tenemos que conocer el RefugeID, FoodRations y WaterLiters, ordenados de menor a mayor por FoodRations. Al ser el primero este será el único ejercicio con respuesta incluida como ejemplo.

Ejercicio 2: Identifica a los refugios mejor armados

Tras localizar los refugios en situación crítica, el Consejo necesita saber quiénes pueden apoyarlos. Los refugios con más armas son los únicos capaces de enviar ayuda o resistir un asedio prolongado.

Tu misión: mostrar los cinco refugios con mayor número de armas (Weapons), mostrando también RefugeID. Piensa cómo limitar los resultados para centrarte en los más fuertes.

Pista: Ordena tus datos de forma que los primeros sean los más peligrosos… o los más protegidos.

Ejercicio 3: Define la zona caliente del mapa

Los exploradores acaban de interceptar señales de radio que indican grandes movimientos de hordas entre las latitudes 39 y 41 y longitudes -75 y -73. Hay que comprobar si los refugios críticos o mejor armados están dentro de esa zona de peligro inminente.

Tu misión: listar los refugios ubicados en ese rango, mostrando RefugeID, Latitude y Longitude.

Pista: Dos coordenadas definen un área. ¿Cómo filtrarías para quedarte solo con los refugios dentro de esa caja imaginaria?

Ejercicio 4: Conoce a los supervivientes que defiendes

La información reunida hasta ahora es valiosa, pero incompleta. Necesitamos saber cuántos habitantes hay en cada refugio para calcular si tienen suficiente comida, agua y armas para sobrevivir. Sin estos datos combinados, cualquier estrategia será un tiro en la oscuridad.

Tu misión: combinar RefugeSupplies y SurvivorStats para mostrar RefugeID, Population, FoodRations y Weapons de cada refugio.

Pista: La información de suministros y la población viven en tablas distintas, pero tienen algo en común: el refugio.

Ejercicio 5: Identifica tragedias inminentes

El análisis anterior revela un peligro alarmante, algunos refugios tienen mucha gente pero apenas armas. Si un mutante llega, la masacre será inmediata y el caos se extenderá a otros refugios.

Tu misión: encontrar los refugios que tienen menos de 5 armas y más de 50 supervivientes. Estas son las prioridades absolutas para enviar refuerzos antes de que el desastre sea irreversible.

Pista: Para detectar refugios sobrepoblados y mal armados, debes comparar datos que no están en la misma tabla. ¿Cómo unirlos y luego aplicar las condiciones?

Consejo de veterano

En producción, la diferencia entre una consulta optimizada y una chapuza es la misma que entre un refugio reforzado y uno que se desmorona al primer ataque. Indexa, revisa planes de ejecución y parametriza: no estamos jugando, estamos sobreviviendo.

¿Quieres las respuestas?

Estoy preparando un vídeo en el canal de YouTube donde resolveremos paso a paso estos ejercicios y explicaremos cada detalle para que no sólo los copies, sino que los entiendas. Eso sí, tendrás que esperar a la semana que viene, mientras tanto intenta resolver estos ejercicios por tu cuenta para practicar y deja en comentarios tus respuestas.

Próximamente…

En el siguiente episodio subiremos la dificultad: cálculos de tasas de infección, clasificaciones y agrupaciones para decidir a quién ayudar primero. Porque en un mundo arrasado por mutantes, saber sumar y agrupar correctamente puede ser lo que te salve.

Publicado por Roberto Carrancio en Cloud, SQL Server, 9 comentarios

El problema del Reenvío de punteros en tablas heap

Hoy vamos a hablar de un fenómeno tan molesto como habitual en ciertos entornos: el reenvío de punteros en tablas heap de SQL Server. Sí, ese “detalle” que suele pasar desapercibido hasta que un día nuestras consultas empiezan a ir como un carro tirado por burros, y claro, toca ponerse el traje de bombero.

Antes de entrar en harina, pongamos un poco de contexto. Las tablas heap, esas nobles estructuras sin índice clustered, a veces se eligen por necesidad, otras por ignorancia y, en los peores casos, porque alguien tenía prisa por entregar y pensó que ya lo arreglaríamos después.Y claro, luego nunca se arregla. Y mientras tanto, los forwarded records o reenvíos de punteros campan a sus anchas.

¿Qué es un reenvío de punteros y por qué debería importarnos?

Cuando una tabla no tiene índice clustered, SQL Server guarda las filas donde buenamente puede. Sin ese ordenamiento y estructura que un clustered impone, el heap es un campo abierto. Hasta aquí, todo bien. El problema aparece cuando actualizamos una fila y esa nueva versión no cabe en la misma página. ¿Qué hace SQL Server? ¿Mover toda la fila a otra página y actualizar los punteros que la referencian? Ojalá. Lo que hace es dejar un puntero en la ubicación original que apunta a la nueva ubicación de la fila. Ese es el reenvío de puntero: un salto innecesario que se añade al acceso de la fila.

Esto, que puede parecer inofensivo en pequeñas dosis, se convierte en un verdadero problema cuando la tabla crece y las modificaciones son frecuentes. Cada reenvío implica un acceso extra a disco o memoria para encontrar la fila real. Y como bien sabemos, esos accesos extra no son gratuitos: incrementan el tiempo de lectura y degradan el rendimiento de las consultas. Especialmente en esas tablas que alguien decidió consultar con un SELECT * y sin WHERE, porque total, ¿qué podría salir mal?

Cómo se genera un reenvío de punteros

Veámoslo en acción. Tenemos una tabla heap. Insertamos filas. Todo perfecto. Llega el día en que un UPDATE aumenta el tamaño de una fila (añadimos datos a una columna VARCHAR, por ejemplo). La fila ya no cabe en su página. SQL Server mueve la fila a una nueva página y deja un puntero en la posición original apuntando a la nueva ubicación. Algo así como el cartel de nos hemos mudado que ponen algunos negocios en su antiguo local cuando cambian de ubicación. Ahora acceder a esa fila implica primero leer la página con el puntero, luego saltar a la nueva página y leer la fila real. Siguiendo con el ejemplo anterior es como si el GPS te llevase al local antiguo para que vieses el cartel y tuvieses que ir al nuevo.

Pero es que la fiesta no termina ahí. Si la fila se vuelve a actualizar y tampoco cabe en su nueva página, obtenemos un reenvío de un reenvío. ¿Bonito, verdad? Sí, tan bonito como ese fragmento lógico que nadie defragmenta porque “total, no pasa nada”.

Impacto en el rendimiento de los reenvíos de punteros

El impacto de los reenvíos de punteros se manifiesta principalmente en las operaciones de lectura. Cada reenvío supone al menos un salto adicional. Si tenemos un número considerable de ellos, nuestras lecturas se convierten en una gincana de páginas de datos, con sus correspondientes cache misses, latches y demás alegría. Pero no creas que las escrituras están exentas, recuerda que antes de escribir casi siempre lees.

Esto no solo afecta a la velocidad de las consultas. También incrementa el uso de CPU, el tráfico de I/O y la presión sobre el buffer pool. Y aquí viene el detalle que más nos gusta: el optimizador de consultas no tiene en cuenta el coste de los reenvíos al planificar. Así que podemos tener un plan que parecía estupendo sobre el papel y que en ejecución se arrastra como un SELECT con hints absurdos.

Cómo detectar el reenvío de punteros

Detectar reenvíos de punteros no es complicado, pero requiere mirar donde hay que mirar. Podemos usar sys.dm_db_index_physical_stats con la opción DETAILED para analizar las tablas heap y ver el número de forwarded_records. Si este número empieza a crecer, es hora de preocuparse.

Un ejemplo de consulta para los amigos de lo rápido y directo:

Esto nos da una idea de cuántos reenvíos tenemos por tabla. Si ese número no es cero, tenemos trabajo pendiente. Y si el número es alto, es probable que también tengamos un problema de rendimiento.

¿Como elimino los reenvíos de punteros?

La primera solución (y la más eficaz) es: no uses heap salvo que tengas un motivo sólido para hacerlo. Si la tabla tiene lecturas frecuentes y actualizaciones que modifican el tamaño de las filas, un índice clustered es casi siempre mejor elección. Sí, incluso aunque esa tabla sea de staging y “solo esté ahí un ratito”.

Si por algún motivo tenemos que seguir con el heap, toca plan de mantenimiento especial. Por norma general, los planes de mantenimiento de índices no nos van a servir. En su lugar, tenemos que usar el comando ALTER TABLE [NombreTabla] REBUILD que reconstruye el heap y elimina los reenvíos de puntero. Pero claro, eso implica bloqueo de tabla (en algunas versiones de SQL) y no es algo que uno quiera hacer en mitad de un horario productivo.

Otra opción es rediseñar el esquema de la tabla para minimizar las columnas de longitud variable que crecen sin control. Porque claro, si tenemos un VARCHAR(8000) para guardar un número de teléfono “por si acaso”, el problema no es el heap: el problema somos nosotros.

Y por supuesto, monitorizar. Tener un job que periódicamente revise el número de forwarded_records y alerte si se dispara es una medida sencilla que nos puede ahorrar muchas sorpresas.

¿Hay situaciones donde el heap tenga sentido?

Sí, existen. Por ejemplo, en tablas de staging para cargas de datos donde no se hacen actualizaciones y los datos se procesan y eliminan rápidamente. Ahí un heap puede funcionar bien. Pero claro, en cuanto se empieza a hacer algún UPDATE, la decisión de no poner un clustered se vuelve difícil de defender. Y no, el argumento de “es que así insertamos más rápido” no vale si luego el rendimiento de las consultas se va al traste.

Conclusión

El reenvío de punteros es un mecanismo interno de SQL Server para lidiar con el crecimiento de filas en tablas heap. No es un bug, es un diseño consciente. El problema es que suele aparecer porque tomamos decisiones de diseño pobres o porque descuidamos el mantenimiento. Si tenemos un heap, debemos ser conscientes de las implicaciones y monitorizar su estado. Y si vemos reenvíos, actuar antes de que el rendimiento se desplome.

Como siempre, lo más efectivo es evitar el problema desde el principio. Un índice clustered bien elegido elimina de raíz el riesgo de reenvíos. Y si alguien nos insiste en que un heap es la mejor opción para esa tabla con 500 millones de filas y actualizaciones frecuentes, siempre podemos sugerirle que haga las pruebas en producción. Total, ¿qué podría salir mal?

Espero que este artículo te haya resultado útil e interesante. Si tienes alguna duda o comentario, no dudes en contactarnos en Twitter o por mail o dejarnos un mensaje en los comentarios de aquí abajo. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir.

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios

¿Es PLE un buen indicador de rendimiento de SQL Server?

Durante años, el Page Life Expectancy (PLE) ha sido una de esas métricas que aparecen en los scripts de monitorización como si fueran mantras sagrados. Una de esas columnas que se miran con recelo, que hacen saltar alertas y que, para algunos, justifican peticiones de más memoria RAM con la ligereza con la que se pide café. Pero como todo en SQL Server, nada es tan sencillo como parece y el PLE, aunque útil, tiene trampa.

¿Qué demonios es el PLE?

El PLE representa cuántos segundos, de media, una página de datos puede permanecer en el buffer pool antes de ser desalojada. Es decir, mide cuánto tiempo vive la información en memoria antes de que SQL Server tenga que expulsarla para hacer sitio a otra. Técnicamente, es un contador de rendimiento (sys.dm_os_performance_counters) que se encuentra dentro de Buffer Manager.

Cuando este valor es alto, respiramos tranquilos: significa que las páginas permanecen en memoria lo suficiente, probablemente porque tenemos un buffer pool holgado y un acceso a disco relajado. Cuando cae en picado, suele ser síntoma de presión de memoria o de lecturas físicas excesivas, muchas veces provocadas por planes de ejecución poco afortunados.

Ahora bien, el número mágico de 300 segundos (5 minutos) que algunos siguen usando como umbral es tan fiable como usar una brújula en mitad de un campo magnético. Ese valor tenía sentido… cuando los servidores venían con 4 GB de RAM. Hoy, con instancias que superan los 512 GB, seguir usando el mismo umbral es como usar una regla de colegio para medir una autopista.

Valores PLE deseables: ¿hay alguno?

Aquí viene la gran pregunta que a su vez es el gran problema del PLE: su valor es absolutamente relativo. Depende del tamaño del buffer pool, del patrón de carga de trabajo, de los tipos de queries que se estén ejecutando, y de si el sistema ha tenido un pico puntual de actividad.

Una instancia con 256 GB de RAM debería tener un PLE mucho más alto que una con 16 GB. ¿Cuánto más? No hay una cifra mágica, pero una orientación razonable sería multiplicar los segundos base (300 en los viejos tiempos) por un factor en relación al tamaño del buffer pool. O mejor aún, establecer una línea base propia de nuestra carga habitual y monitorizar desviaciones.

Porque eso es lo que importa: no tanto si el PLE es 2.000 o 20.000, sino si ha caído bruscamente respecto a lo normal. Un descenso repentino suele estar vinculado a algo que no encaja: una query que hace lecturas absurdas, un mantenimiento mal planteado, o simplemente un usuario que ha decidido escanear toda la tabla de movimientos de 200 millones de registros “por si acaso”.

El PLE no es una medida mágica

Muchos lo tratan como si fuera el santo grial de la salud del servidor, pero en realidad el PLE no mide nada en términos de experiencia de usuario. Es un indicador indirecto de presión de memoria, no una medida de rendimiento ni de latencia. Puede estar alto mientras el sistema responde lento, o estar bajo y aun así tener una experiencia fluida si las queries están bien cacheadas.

Además, el PLE es un promedio a nivel de NUMA node, lo que significa que puede estar sesgado si tenemos un servidor con múltiples nodos y la presión se concentra solo en uno de ellos. SQL Server calcula un PLE por cada nodo NUMA, pero los scripts que aglutinan el valor total no siempre lo desglosan correctamente. De ahí que convenga analizarlo por nodo para tener una visión clara.

Y por si fuera poco, el PLE se resetea con cada reinicio de la instancia. Así que no, si ves un valor bajo justo después de un restart, no es el fin del mundo: es simplemente lo que hay.

Detractores: con razón, no por moda

Los que critican el PLE no lo hacen por capricho. Lo hacen porque, en muchos entornos, mirar el PLE sin contexto lleva a diagnósticos erróneos y decisiones equivocadas. Se han visto DBAs pidiendo 128 GB de RAM más “porque el PLE está bajo”, sin pararse a mirar que el problema era una consulta sin WHERE que se colaba a producción cada viernes a las 15:00.

El PLE tampoco distingue entre lecturas necesarias y lecturas absurdas. Si haces un SELECT * de una tabla de logs históricos porque alguien quiere exportarla a Excel “por si acaso”, el PLE caerá en picado igual que si tuvieras una mala estrategia de índices. Así que usarlo como medida absoluta de salud es, como poco, ingenuo.

Alternativas: mirar más allá del PLE

Si queremos una visión más rica, hay vida más allá del PLE. Podemos observar métricas como el Buffer Cache Hit Ratio, aunque también con cautela, porque este valor suele estar cerca del 100% en casi todas las instancias modernas y no siempre significa lo que creemos. Lo que realmente nos interesa es entender qué queries están provocando lecturas físicas excesivas.

Aquí entra en juego la DMV sys.dm_exec_query_stats, que combinado con sys.dm_exec_sql_text y sys.dm_exec_query_plan, nos puede dar visibilidad sobre qué consultas están provocando lecturas físicas o lógicas desproporcionadas. También podemos revisar el sys.dm_io_virtual_file_stats para analizar el I/O por base de datos y archivo. 

Y si lo que nos interesa es el uso de memoria, el sys.dm_os_memory_clerks y el sys.dm_os_buffer_descriptors ofrecen información mucho más granular sobre cómo SQL Server está usando realmente la RAM.

Además, a partir de SQL server 2025 tendremos la nueva DMV sys.dm_os_memory_health_history pero eso da para otro artículo.

En resumen: el PLE puede servirnos como un primer vistazo, como ese canario en la mina que nos avisa de que algo pasa. Pero confiar ciegamente en él es como juzgar un libro por el grosor del lomo.

Monitorizar el PLE: ¿sí o no?

Entonces, ¿monitorizamos el PLE o no? Esta es una de esas preguntas que genera debates infinitos y respuestas del tipo “depende”. Yo voy a ser neutral, no voy a entrar en la trinchera de los que lo consideran inútil ni en la de los que lo elevan al nivel de oráculo. Lo que sí diré es que monitorizar el PLE puede tener sentido si sabemos lo que estamos mirando y no nos dejamos llevar por interpretaciones simplistas.

Si tenemos una línea base sólida, si entendemos la arquitectura NUMA de nuestra instancia y si usamos el PLE como un indicador más dentro de un conjunto más amplio de métricas, entonces puede ser útil. Pero si lo usamos como termómetro único del rendimiento, vamos a acabar medicando al paciente por fiebre sin saber que tiene apendicitis.

Conclusión

El Page Life Expectancy no está muerto, pero tampoco es un mesías. Es una métrica con contexto, con historia y con limitaciones. Sirve para levantar sospechas, no para dictar sentencias. Hay que leerlo con ojo clínico, entender lo que implica y, sobre todo, combinarlo con otras métricas más modernas y más específicas.

Como siempre, no hay atajos. Lo que hay es análisis, observación y un poco de sentido común. Que no es mucho pedir… salvo que creas que SELECT * sigue siendo buena idea.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios