SQL · Capítulo 6 · Lección de conceptos · 6 min de lectura

Calcular un valor nuevo para cada fila

CASE, las funciones de fecha y las de texto hacen lo mismo: calculan un valor nuevo a partir de cada fila. Cuando ese valor existe, puedes agruparlo, contarlo y sumarlo como cualquier columna.

La idea

Las columnas originales rara vez coinciden con la forma en que habla la gente. Finanzas quiere rangos de precio, no precios. Ventas quiere meses, no timestamps. Marketing quiere 'CA', no ' ca'. Las herramientas de este capítulo, CASE, las funciones de fecha y las de texto, hacen una sola cosa: calculan un valor nuevo a partir de cada fila.

Ese valor nuevo es, desde ese momento, una columna normal. Puedes agruparla, contarla, sumarla y ordenar por ella. El truco de todo el capítulo es calcular primero el valor y después usar las herramientas que ya conoces.

Figura 1

CASE prueba las ramas de arriba abajo y se detiene en la primera verdadera

products
product_idnameunit_price
104Upland Oak Bookcase429.00
106Northfield Rattan Sectional229.00
107Birch Teak Side Table169.00
152Monroe Stoneware Utensil Set13.99
Resultado de CASE por fila
product_idprice_band
104200_plus
106200_plus
10750_to_199
152under_50
GROUP BY price_band
price_bandproducts
200_plus2
50_to_1991
under_501

Paso 1 de 5 Cuatro productos con precio. Finanzas quiere rangos, no precios: menos de 50, de 50 a 199, y 200 o más. CASE WHEN unit_price < 50 THEN 'under_50' WHEN unit_price < 200 THEN '50_to_199' ELSE '200_plus' END.

  1. Cuatro productos con precio. Finanzas quiere rangos, no precios: menos de 50, de 50 a 199, y 200 o más. CASE WHEN unit_price < 50 THEN 'under_50' WHEN unit_price < 200 THEN '50_to_199' ELSE '200_plus' END.
  2. El Utensil Set cuesta 13.99. La primera rama, unit_price < 50, es verdadera. La base de datos se detiene ahí y nunca mira las demás ramas.
  3. El Side Table cuesta 169.00. La primera rama es falsa, la segunda (< 200) es verdadera, así que recibe 50_to_199. Esto funciona solo porque las filas baratas ya se atraparon arriba.
  4. El Bookcase y el Sectional no cumplen ningún WHEN, así que ELSE les da 200_plus. Sin un ELSE recibirían NULL.
  5. Ahora price_band es una columna normal. GROUP BY price_band pliega las cuatro filas en tres conteos.

Por qué existe

Los datos se guardan para ser exactos, no para hacer informes. El timestamp de un pedido es exacto al segundo, un producto tiene su precio al centavo y un estado lo escribió quien llenó el formulario. Ninguno de esos es la categoría que una persona quiere ver en un gráfico. Antes de funciones como estas, exportabas a una hoja de cálculo, agregabas columnas auxiliares a mano y repetías el trabajo cada mes. En SQL la regla vive en la consulta, así que todos obtienen los mismos rangos, los mismos meses y el mismo texto limpio, siempre.

Cada herramienta además deja intacta la columna original. Puedes mostrar el precio y el rango lado a lado, el timestamp y el mes, el estado escrito y el limpio, que es la mejor forma de comprobar tu regla contra los datos antes de fiarte de ella.

Cómo funciona por dentro

Todo esto son expresiones, y las expresiones se evalúan fila por fila, durante el SELECT o dentro de una cláusula que necesita un valor. Nunca miran las filas vecinas.

CASE: gana la primera rama verdadera

CASE prueba sus condiciones WHEN en el orden en que las escribiste, devuelve el valor de la primera que es verdadera y se detiene. Si ninguna lo es, devuelve el valor de ELSE, o NULL cuando no hay ELSE. Dos consecuencias: pon primero la condición más estrecha, y decide qué debe pasar con las filas en las que no pensaste, en vez de dejar que caigan en ELSE por accidente. Un precio NULL falla toda comparación con <, así que cae en ELSE a menos que pruebes IS NULL antes.

Fechas: redondear hacia abajo y luego agrupar

Un timestamp casi nunca es igual a otro, así que agrupar por él no sirve. DATE_TRUNC('month', order_date) redondea cada valor hacia abajo al primer día de su mes, y así aparecen valores iguales.

Figura 2

DATE_TRUNC redondea cada timestamp hacia abajo al inicio de su mes

orders
order_idorder_date
11902023-01-31 12:58:16
11912023-01-31 15:10:33
11922023-01-31 15:18:57
11932023-02-01 13:15:26
11942023-02-01 16:36:32
DATE_TRUNC('month', order_date)
order_idorder_month
11902023-01-01
11912023-01-01
11922023-01-01
11932023-02-01
11942023-02-01
GROUP BY order_month
order_monthorders
2023-01-013
2023-02-012

Paso 1 de 4 Cinco pedidos con timestamp completo. No hay dos iguales, así que agrupar por order_date daría cinco grupos de uno.

  1. Cinco pedidos con timestamp completo. No hay dos iguales, así que agrupar por order_date daría cinco grupos de uno.
  2. DATE_TRUNC('month', order_date) reemplaza cada timestamp por el primer instante de su mes. Los tres pedidos de enero pasan a ser 2023-01-01, sin importar el día ni la hora.
  3. Los pedidos 1193 y 1194 son del 1 de febrero, así que pasan a ser 2023-02-01. Un día después, otro grupo.
  4. Ahora existen valores iguales, así que GROUP BY puede plegarlos: 3 pedidos en enero de 2023 y 2 en febrero. El año es parte del valor, así que enero de 2023 nunca se mezcla con enero de 2024.

Truncar conserva el año, así que los ingresos mensuales de tres años dan 36 filas, en orden. Es distinto de extraer el número del mes, que da 1 a 12 y junta todos los eneros en uno. Usa truncar para una serie de tiempo y extraer cuando de verdad quieres "todos los eneros" o "qué día de la semana". Los nombres de las funciones varían según la base de datos: DATE_TRUNC en Postgres, DuckDB y BigQuery (con otro orden de argumentos en BigQuery), DATE_FORMAT en MySQL. La idea es idéntica.

El tiempo entre dos fechas

Restar fechas da una duración, y una duración es un número de días: la fecha de devolución menos la fecha del pedido es lo que tardó en volver un artículo. Ambos lados deben ser fechas del mismo tipo. Un timestamp menos una fecha puede dejarte horas y minutos, o un error de tipo, según el motor. Convierte ambos a DATE primero y la respuesta será un número entero de días que puedes promediar o agrupar con un CASE. La sintaxis vuelve a diferir entre bases de datos, así que revisa la función una vez y reutilízala.

Agregación condicional

Un CASE dentro de un agregado permite que un solo GROUP BY responda varias preguntas a la vez. Escribe la condición como 1 o 0, súmala, y tendrás un conteo de las filas que cumplen. Divide entre COUNT(*) para obtener una tasa.

Figura 3

SUM(CASE ...) convierte un sí/no en 1 o 0, así un solo GROUP BY cuenta ambos

orders
order_idchannelstatus
1003marketplacecompleted
1004marketplacecompleted
1005webcompleted
1006marketplacecancelled
1007marketplacecompleted
1008webcompleted
CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END
order_idchannelwas_cancelled
1003marketplace0
1004marketplace0
1005web0
1006marketplace1
1007marketplace0
1008web0
GROUP BY channel
channelorderscancelledcancel_rate
marketplace4125.0
web200.0

Paso 1 de 4 ¿Qué parte de los pedidos se cancela, por canal? Necesitas dos números por grupo: todos los pedidos y los pedidos cancelados.

  1. ¿Qué parte de los pedidos se cancela, por canal? Necesitas dos números por grupo: todos los pedidos y los pedidos cancelados.
  2. El CASE corre en cada fila y escribe 1 para un pedido cancelado y 0 para el resto. Solo el pedido 1006 recibe un 1.
  3. GROUP BY channel pliega los montones. COUNT(*) cuenta todas las filas de cada montón y SUM(was_cancelled) suma los 1: marketplace tiene 4 pedidos y 1 cancelado.
  4. Web tiene 2 pedidos y ningún 1. Divide los dos números dentro de la misma consulta y obtienes una tasa por canal: 25.0 y 0.0 por ciento.

Esto supera a ejecutar una consulta por condición, porque todos los números salen del mismo montón de filas. La tasa de cancelación, la de devolución y la proporción de pedidos con cupón pueden estar una junto a otra en un solo resultado.

Texto: limpia antes de agrupar

TRIM, UPPER, LOWER y COALESCE devuelven una copia limpia del valor para esta consulta. La tabla nunca cambia. Si ' ca' y 'CA' deben ser el mismo estado, limpia la columna en el SELECT y agrupa también por la versión limpia, ya que GROUP BY solo ve lo que le dices que vea. COALESCE(value, 'unknown') le pone etiqueta a un NULL para que aparezca como su propia fila y no como un espacio en blanco.

Errores comunes

"CASE elige la rama que mejor se ajusta"

Elige la primera verdadera, de arriba abajo. Invierte el orden de las dos ramas de precio y todo producto de menos de $200 queda etiquetado como mid, incluso los baratos. Ningún error te avisa. Cuando un rango se ve demasiado grande, lee las ramas en orden como lo hace la base de datos.

"Agrupar por el número del mes me da totales mensuales"

Te da cubetas de mes a través de los años: enero de 2023, 2024 y 2025 en una sola fila, lo que parece un pico. Si un mes se ve el doble de lo esperado, comprueba antes que nada si se perdió el año. Trunca al mes, para que el año se quede en el valor.

"TRIM y UPPER arreglan los datos"

Arreglan la salida de una consulta. Los espacios sobrantes siguen en la tabla mañana, y otra consulta que olvide el TRIM contará ' CA' por separado. La limpieza va en la consulta para el análisis, y en el sistema de origen si puedes llegar a él. Di cuál de las dos hiciste cuando informes los números.

Ponte a prueba

Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.

Un producto cuesta 40. Tu CASE dice WHEN unit_price < 200 THEN 'mid' WHEN unit_price < 50 THEN 'cheap' ELSE 'premium' END. ¿Qué etiqueta recibe?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.