Contenido de esta guía
La idea: CASE crea la categoría y GROUP BY la resume
Cuando una columna contiene valores continuos —precios, edades, puntuaciones, tiempos o existencias— a menudo necesitas convertirlos en rangos comprensibles. Una expresión CASE puede asignar una etiqueta a cada fila y GROUP BY puede reunir después todas las filas que recibieron la misma etiqueta.
El modelo mental es sencillo: valor original → CASE decide el tramo → GROUP BY reúne el tramo → las agregaciones lo resumen. La parte delicada no es la sintaxis, sino diseñar condiciones que cubran todos los casos una sola vez.
Por ejemplo, para pedidos puedes definir tres intervalos: menos de 100 €, desde 100 hasta menos de 200 €, y 200 € o más. Si ordenas las condiciones de menor a mayor, cada pedido entra en el primer WHEN verdadero.
Patrón general
SELECT
CASE
WHEN valor < limite_1 THEN 'tramo 1'
WHEN valor < limite_2 THEN 'tramo 2'
ELSE 'tramo 3'
END AS tramo,
COUNT(*) AS filas
FROM tabla
GROUP BY
CASE
WHEN valor < limite_1 THEN 'tramo 1'
WHEN valor < limite_2 THEN 'tramo 2'
ELSE 'tramo 3'
END;Las dos apariciones de CASE deben representar exactamente la misma clasificación. Repetir la expresión es verboso, pero evita depender de que un motor permita usar el alias del SELECT dentro de GROUP BY. Si la clasificación se vuelve larga, una CTE o una subconsulta puede calcular el tramo una sola vez y agruparlo después.
Ejemplo comprobado: pedidos por importe
La base educativa contiene 18 pedidos. Vamos a clasificarlos en tres tramos y a calcular cuántos pedidos y cuánto importe total hay en cada uno.
SELECT
CASE
WHEN total < 100 THEN 'Menos de 100 €'
WHEN total < 200 THEN '100 a 199,99 €'
ELSE '200 € o más'
END AS tramo,
COUNT(*) AS pedidos,
ROUND(SUM(total), 2) AS importe
FROM pedidos
GROUP BY
CASE
WHEN total < 100 THEN 'Menos de 100 €'
WHEN total < 200 THEN '100 a 199,99 €'
ELSE '200 € o más'
END
ORDER BY MIN(total);| tramo | pedidos | importe |
|---|---|---|
| Menos de 100 € | 9 | 507.70 |
| 100 a 199,99 € | 4 | 635.30 |
| 200 € o más | 5 | 2788.90 |
Los 9 + 4 + 5 pedidos suman los 18 registros de la tabla, una comprobación útil para detectar huecos. Los importes también suman 3931.90 €, el total de la tabla. ORDER BY MIN(total) ordena los grupos por el menor valor real de cada tramo en lugar de por el texto de la etiqueta.
Diseña límites sin huecos ni solapamientos
Una forma segura de construir intervalos crecientes es usar solo el límite superior en cada WHEN. Después de fallar total < 100, la segunda condición total < 200 significa de forma implícita “100 o más y menos de 200”. No necesitas repetir total >= 100.
Solapar condiciones
CASE
WHEN total <= 100 THEN 'bajo'
WHEN total >= 100 AND total < 200 THEN 'medio'
...
ENDEl valor 100 cumple las dos condiciones. CASE devolverá la primera porque evalúa de arriba abajo, pero la definición de los rangos queda ambigua. Decide de antemano si cada límite pertenece al tramo anterior o al siguiente.
Dejar valores fuera
Si escribes un último WHEN demasiado específico y omites ELSE, las filas no cubiertas reciben NULL. Eso puede crear un grupo nulo que no esperabas. Usa ELSE cuando exista una categoría residual válida o trata explícitamente los valores que deben quedar fuera.
Qué hacer con NULL
Una comparación como total < 100 no es verdadera cuando total es NULL. Si tienes datos nulos, decide su significado antes de agrupar. Puedes asignarles una categoría propia con un WHEN total IS NULL THEN 'Sin dato' colocado antes de los rangos numéricos.
CASE
WHEN total IS NULL THEN 'Sin dato'
WHEN total < 100 THEN 'Menos de 100 €'
WHEN total < 200 THEN '100 a 199,99 €'
ELSE '200 € o más'
ENDNo conviertas NULL en cero por comodidad si “dato desconocido” y “importe cero” significan cosas distintas en tu dominio.
Evita repetir un CASE largo con una CTE
Cuando la clasificación tiene muchas ramas, puedes calcular primero el tramo y agrupar después. Esto separa dos responsabilidades: una etapa clasifica filas y otra produce el resumen.
WITH pedidos_clasificados AS (
SELECT total,
CASE
WHEN total < 100 THEN 'Menos de 100 €'
WHEN total < 200 THEN '100 a 199,99 €'
ELSE '200 € o más'
END AS tramo
FROM pedidos
)
SELECT tramo,
COUNT(*) AS pedidos,
ROUND(SUM(total), 2) AS importe
FROM pedidos_clasificados
GROUP BY tramo;Esta forma no es “más rápida” por definición. Su ventaja pedagógica es que hace visible la secuencia clasificar → resumir. El rendimiento real depende del motor, del plan y de los datos.
Compatibilidad entre motores
CASE es una expresión estándar ampliamente soportada y puede utilizarse donde el motor admite expresiones. PostgreSQL documenta que CASE puede aparecer donde una expresión sea válida; MySQL permite expresiones en GROUP BY; SQL Server admite cálculos no agregados como expresiones de agrupación; SQLite evalúa cada expresión de GROUP BY para asignar filas a grupos.
Lo que sí puede variar es si puedes agrupar directamente por un alias definido en el mismo SELECT. Para una guía portable, repite el CASE o calcula el alias en una CTE/subconsulta y agrupa en la consulta exterior.
Comprueba lo aprendido
Resume productos por nivel de stock
Con la tabla productos, crea tres grupos: Sin stock cuando stock = 0, Stock bajo cuando sea menor que 10 y Stock suficiente para el resto. Devuelve el nombre del grupo y cuántos productos contiene.
Criterio de éxito: el resultado debe contener tres filas con 2, 4 y 3 productos respectivamente.
stock = 0 antes que stock < 10.ELSE para el stock suficiente.CASE que utilizas para producir la etiqueta.Solución razonada
SELECT
CASE
WHEN stock = 0 THEN 'Sin stock'
WHEN stock < 10 THEN 'Stock bajo'
ELSE 'Stock suficiente'
END AS nivel_stock,
COUNT(*) AS productos
FROM productos
GROUP BY
CASE
WHEN stock = 0 THEN 'Sin stock'
WHEN stock < 10 THEN 'Stock bajo'
ELSE 'Stock suficiente'
END
ORDER BY MIN(stock);La base educativa devuelve Sin stock = 2, Stock bajo = 4 y Stock suficiente = 3. La condición de cero va primero porque también cumple stock < 10.
Qué debes recordar
CASEtransforma cada valor en una categoría;GROUP BYreúne las filas de esa categoría.- Ordena condiciones crecientes para que los rangos sean fáciles de razonar.
- Evita límites solapados y comprueba que no queden huecos.
- Trata
NULLde forma explícita si “sin dato” merece una categoría propia. - Para un
CASElargo, una CTE puede separar la clasificación del resumen sin implicar una mejora automática de rendimiento. - Comprueba que la suma de los conteos por tramo coincide con el total de filas que esperabas clasificar.