Saltar al contenido
case.sql · devschool

CASE en SQL

Lección 7 de 22 · 15 min de lectura · Actualizado el

En esta lección
  1. CASE buscado: condiciones una tras otra
  2. CASE simple: comparar con valores concretos
  3. ELSE y el NULL por defecto
  4. CASE en ORDER BY: un orden a medida
  5. CASE dentro de funciones de agregación
  6. CASE en UPDATE
  7. IF() en MySQL y alternativas
  8. Errores frecuentes
  9. Resumen

Muchas veces no quieres mostrar un dato tal cual, sino según su valor: “Menor” si el cliente tiene menos de 18 años, “Agotado” si el stock es 0, “Envío gratis” si el pedido pasa de 100 €. En un lenguaje de programación usarías un if. En SQL tienes CASE, una expresión que evalúa condiciones y devuelve un valor distinto para cada caso.

CASE es SQL estándar, funciona igual en MySQL, PostgreSQL y SQLite, y se puede usar en casi cualquier sitio: en el SELECT, en el ORDER BY, dentro de SUM o COUNT y en un UPDATE. En esta lección verás todos esos usos con las tablas del curso.

CASE buscado: condiciones una tras otra

La forma más completa se llama CASE buscado. Tiene esta estructura:

CASE
  WHEN condicion1 THEN valor1
  WHEN condicion2 THEN valor2
  ELSE valor_por_defecto
END

La base de datos comprueba las condiciones de arriba abajo y devuelve el valor de la primera que se cumple. Si no se cumple ninguna, devuelve el del ELSE. Todo el bloque, de CASE a END, es un único valor, así que se le pone un alias con AS como a cualquier columna calculada.

Clasificar clientes por edad

SELECT nombre, edad,
       CASE
         WHEN edad < 18 THEN 'Menor'
         WHEN edad < 30 THEN 'Joven'
         WHEN edad < 45 THEN 'Adulto'
         ELSE 'Senior'
       END AS tramo
FROM clientes;
nombreedadtramo
Ana García34Adulto
Luis Pérez22Joven
Marta Ruiz45Senior
Pablo Díaz17Menor
Eva Martín29Joven

Fíjate en que la segunda condición es solo edad < 30, no edad >= 18 AND edad < 30. No hace falta: si un cliente llega a la segunda línea es porque no cumplía la primera, así que ya sabemos que tiene 18 o más. Pablo (17) se queda en la primera y las demás ni se miran. Marta tiene justo 45: no cumple edad < 45 y cae en el ELSE.

El orden de los WHEN importa

Como gana la primera condición verdadera, el orden cambia el resultado. Mira qué pasa si las escribes al revés:

SELECT nombre, edad,
       CASE
         WHEN edad < 45 THEN 'Adulto'
         WHEN edad < 30 THEN 'Joven'
         WHEN edad < 18 THEN 'Menor'
         ELSE 'Senior'
       END AS tramo
FROM clientes;
nombreedadtramo
Ana García34Adulto
Luis Pérez22Adulto
Marta Ruiz45Senior
Pablo Díaz17Adulto
Eva Martín29Adulto

Pablo, con 17 años, sale como “Adulto”: 17 < 45 es verdadero y la base de datos no sigue mirando. No da ningún error, simplemente devuelve datos incorrectos. Regla práctica: con rangos, ordena las condiciones de la más restrictiva a la más amplia.

Clasificar pedidos por importe

El mismo patrón sirve para cualquier columna numérica:

SELECT id, importe,
       CASE
         WHEN importe < 20 THEN 'Pequeño'
         WHEN importe <= 60 THEN 'Mediano'
         ELSE 'Grande'
       END AS tamanio
FROM pedidos;
idimportetamanio
145.00Mediano
2120.50Grande
312.99Pequeño
460.00Mediano
533.25Mediano

El pedido 4 (60.00) es “Mediano” porque la condición es <= 60. Decide siempre qué pasa exactamente en los límites: es donde aparecen los fallos.

En las condiciones vale todo lo del WHERE: AND, OR, BETWEEN, IN, LIKE, IS NULL…

CASE simple: comparar con valores concretos

Cuando todas las condiciones son del tipo “esta columna es igual a tal valor”, hay una forma más corta, el CASE simple. La columna se escribe una sola vez, justo después de CASE:

SELECT nombre, ciudad,
       CASE ciudad
         WHEN 'Santander' THEN 'Cantabria'
         WHEN 'Bilbao'    THEN 'País Vasco'
         WHEN 'Madrid'    THEN 'Comunidad de Madrid'
       END AS comunidad
FROM clientes;
nombreciudadcomunidad
Ana GarcíaSantanderCantabria
Luis PérezBilbaoPaís Vasco
Marta RuizSantanderCantabria
Pablo DíazMadridComunidad de Madrid
Eva MartínMadridComunidad de Madrid

Es equivalente a escribir WHEN ciudad = 'Santander' THEN ... en un CASE buscado. El simple es más legible, pero solo compara por igualdad: no admite <, BETWEEN ni LIKE, y tampoco sirve para detectar NULL, porque NULL = NULL no es verdadero (lo viste en valores NULL):

SELECT CASE NULL WHEN NULL THEN 'es null' ELSE 'no' END AS simple,
       CASE WHEN NULL IS NULL THEN 'es null' ELSE 'no' END AS buscado;
simplebuscado
noes null
CASE simpleCASE buscado
FormaCASE columna WHEN valor THEN ...CASE WHEN condicion THEN ...
ComparacionesSolo igualdadCualquier condición
Detecta NULLNoSí, con IS NULL
Cuándo usarloTraducir códigos o valores fijosRangos y condiciones compuestas

Consejo: si una tabla de traducción como la de las comunidades crece mucho o cambia a menudo, no la escribas en un CASE: crea una tabla ciudades y usa un JOIN. El CASE es para reglas pequeñas y estables.

ELSE y el NULL por defecto

El ELSE es opcional. Si lo omites y no se cumple ninguna condición, CASE devuelve NULL:

SELECT id, importe,
       CASE WHEN importe >= 100 THEN 'Envío gratis' END AS envio
FROM pedidos;
idimporteenvio
145.00NULL
2120.50Envío gratis
312.99NULL
460.00NULL
533.25NULL

A veces es justo lo que quieres (lo usarás enseguida con COUNT). Pero si no lo es, escribe siempre un ELSE, aunque sea ELSE 'Otro'. Así, si mañana aparece un cliente de Sevilla en la consulta de comunidades, verás “Otro” en lugar de un NULL que no sabrás de dónde viene.

Otro detalle: todos los THEN y el ELSE deberían devolver el mismo tipo de dato. Mezclar un número y un texto (THEN 0 ELSE 'sin dato') da error en PostgreSQL y, en MySQL y SQLite, resultados difíciles de ordenar y comparar.

CASE en ORDER BY: un orden a medida

ORDER BY ordena de forma alfabética o numérica, pero a veces el orden lo marca el negocio. Por ejemplo, la tienda está en Santander y quiere ver primero a sus clientes locales y después al resto:

SELECT nombre, ciudad
FROM clientes
ORDER BY CASE WHEN ciudad = 'Santander' THEN 0 ELSE 1 END,
         ciudad, nombre;
nombreciudad
Ana GarcíaSantander
Marta RuizSantander
Luis PérezBilbao
Eva MartínMadrid
Pablo DíazMadrid

El CASE convierte cada ciudad en un número (0 para Santander, 1 para las demás) y se ordena por ese número. Los empates se resuelven con las siguientes columnas, como aprendiste en ORDER BY. Es el truco habitual para ordenar estados como “urgente, normal, bajo” o los días de la semana, que alfabéticamente saldrían desordenados.

CASE dentro de funciones de agregación

Este es el uso más potente y aparece en muchos informes. Las funciones de agregación (COUNT, SUM…) resumen muchas filas en un valor. Si les pasas un CASE, puedes contar o sumar solo las filas que cumplen una condición, varias condiciones distintas en la misma consulta.

Contar con SUM(CASE WHEN … THEN 1 ELSE 0 END)

¿Cuántos clientes son mayores de edad y cuántos no?

SELECT
  COUNT(*) AS total,
  SUM(CASE WHEN edad >= 18 THEN 1 ELSE 0 END) AS adultos,
  SUM(CASE WHEN edad < 18 THEN 1 ELSE 0 END) AS menores
FROM clientes;
totaladultosmenores
541

El CASE convierte cada fila en un 1 (si cumple) o un 0 (si no), y SUM suma esos unos.

Una variante equivalente usa COUNT sin ELSE. Como COUNT no cuenta los NULL, y el CASE sin ELSE devuelve NULL cuando no se cumple la condición, solo cuenta las filas que sí la cumplen:

SELECT
  SUM(CASE WHEN importe >= 50 THEN importe ELSE 0 END) AS ventas_grandes,
  SUM(CASE WHEN importe < 50 THEN importe ELSE 0 END) AS ventas_pequenias,
  COUNT(CASE WHEN importe >= 50 THEN 1 END) AS pedidos_grandes
FROM pedidos;
ventas_grandesventas_pequeniaspedidos_grandes
180.5091.242

Aquí SUM no suma unos, sino el propio importe: 120.50 + 60.00 = 180.50 para los pedidos de 50 € o más.

Una tabla de doble entrada con GROUP BY

Combinado con GROUP BY, que verás en dos lecciones, obtienes un recuento por ciudad y por tramo, como una tabla dinámica de una hoja de cálculo:

SELECT ciudad,
       COUNT(*) AS clientes,
       SUM(CASE WHEN edad >= 18 THEN 1 ELSE 0 END) AS adultos,
       SUM(CASE WHEN edad < 18 THEN 1 ELSE 0 END) AS menores
FROM clientes
GROUP BY ciudad;
ciudadclientesadultosmenores
Bilbao110
Madrid211
Santander220

También puedes agrupar por el resultado de un CASE para contar cuántos pedidos hay en cada tramo:

SELECT
  CASE WHEN importe < 50 THEN 'Menos de 50' ELSE '50 o más' END AS tramo,
  COUNT(*) AS pedidos,
  SUM(importe) AS total
FROM pedidos
GROUP BY tramo;
tramopedidostotal
50 o más2180.50
Menos de 50391.24

Nota: agrupar por un alias (GROUP BY tramo) funciona en MySQL, PostgreSQL y SQLite, aunque el estándar no lo recoge. En otros sistemas, como SQL Server o versiones antiguas de Oracle, tienes que repetir el CASE completo en el GROUP BY.

Consejo: en MySQL y SQLite una comparación vale 1 si es verdadera y 0 si es falsa, así que SUM(edad >= 18) también cuenta los adultos. Es cómodo, pero no funciona en PostgreSQL. SUM(CASE ...) funciona en todas partes.

CASE en UPDATE

CASE también sirve para calcular el nuevo valor en un UPDATE. Para verlo, creamos una tabla de productos de una tienda de montaña:

CREATE TABLE productos (
  id INT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL,
  categoria VARCHAR(50) NOT NULL,
  precio DECIMAL(8, 2) NOT NULL,
  stock INT NOT NULL DEFAULT 0
);

INSERT INTO productos VALUES
  (1, 'Pies de gato', 'Escalada', 89.90, 12),
  (2, 'Arnés', 'Escalada', 59.00, 0),
  (3, 'Mochila 30 L', 'Montaña', 45.50, 3),
  (4, 'Tienda 2 plazas', 'Camping', 120.00, 7),
  (5, 'Linterna frontal', 'Camping', 24.95, 25);

Primero, un SELECT que muestra el estado del stock en la web:

SELECT nombre, stock,
       CASE
         WHEN stock = 0 THEN 'Agotado'
         WHEN stock <= 5 THEN 'Últimas unidades'
         ELSE 'Disponible'
       END AS estado
FROM productos;
nombrestockestado
Pies de gato12Disponible
Arnés0Agotado
Mochila 30 L3Últimas unidades
Tienda 2 plazas7Disponible
Linterna frontal25Disponible

Ahora llegan las rebajas: un 10 % en escalada, un 15 % en camping y nada en el resto. Sin CASE necesitarías dos UPDATE. Con CASE, uno solo:

UPDATE productos
SET precio = CASE categoria
               WHEN 'Escalada' THEN ROUND(precio * 0.90, 2)
               WHEN 'Camping'  THEN ROUND(precio * 0.85, 2)
               ELSE precio
             END;

SELECT id, nombre, categoria, precio FROM productos;
idnombrecategoriaprecio
1Pies de gatoEscalada80.91
2ArnésEscalada53.10
3Mochila 30 LMontaña45.50
4Tienda 2 plazasCamping102.00
5Linterna frontalCamping21.21

El ELSE precio es imprescindible. Sin él, la mochila (categoría Montaña) no cumpliría ningún WHEN, el CASE devolvería NULL y el UPDATE intentaría dejarla sin precio. Aquí la columna es NOT NULL y daría error; si no lo fuera, perderías el precio sin darte cuenta.

Cuidado: este UPDATE no tiene WHERE y toca todas las filas a propósito. Antes de ejecutarlo, prueba el mismo CASE en un SELECT para ver los precios nuevos junto a los antiguos.

IF() en MySQL y alternativas

Cuando solo hay dos posibilidades, MySQL ofrece una función más corta: IF(condicion, valor_si_verdadero, valor_si_falso).

-- Solo MySQL y MariaDB
SELECT nombre, IF(edad >= 18, 'Sí', 'No') AS mayor_de_edad
FROM clientes;
nombremayor_de_edad
Ana GarcíaSí
Luis PérezSí
Marta RuizSí
Pablo DíazNo
Eva MartínSí

Es cómoda, pero no es estándar: PostgreSQL no la tiene, y SQLite y SQL Server usan otro nombre, IIF(condicion, a, b). Además, anidar varios IF para más de dos casos (IF(edad < 18, 'Menor', IF(edad < 30, 'Joven', 'Adulto'))) se lee mucho peor que un CASE.

SistemaDos casosVarios casos
MySQL / MariaDBIF(cond, a, b)CASE
PostgreSQLCASECASE
SQLiteIIF(cond, a, b)CASE

Errores frecuentes

  • Olvidar el END. Es el fallo más común. El mensaje suele ser poco claro (syntax error near FROM), porque la base de datos sigue leyendo el CASE hasta que encuentra algo que no encaja. Cada CASE necesita su END.
  • Olvidar el ELSE y obtener NULL. Sin ELSE, las filas que no cumplen ningún WHEN salen como NULL. En un UPDATE puede borrar datos.
  • Poner los WHEN en mal orden. Con rangos, gana la primera condición verdadera. WHEN edad < 45 antes de WHEN edad < 18 clasifica a todos como adultos.
  • Separar los WHEN con comas. Se escriben uno detrás de otro, sin comas: WHEN a THEN 1 WHEN b THEN 2.
  • Usar el CASE simple para buscar NULL. CASE columna WHEN NULL nunca se cumple. Usa CASE WHEN columna IS NULL.
  • Mezclar tipos en los THEN: números en unos, textos en otros.
  • Copiar IF() de MySQL a PostgreSQL. No existe. CASE funciona en todas partes.

Resumen

Quieres…Escribe
Clasificar por rangosCASE WHEN x < 18 THEN 'a' WHEN x < 30 THEN 'b' ELSE 'c' END
Traducir valores fijosCASE ciudad WHEN 'Bilbao' THEN 'País Vasco' ... END
Un orden a medidaORDER BY CASE WHEN ciudad = 'Santander' THEN 0 ELSE 1 END
Contar los que cumplen algoSUM(CASE WHEN cond THEN 1 ELSE 0 END) o COUNT(CASE WHEN cond THEN 1 END)
Sumar solo algunos importesSUM(CASE WHEN cond THEN importe ELSE 0 END)
Actualizar con reglas distintasUPDATE t SET col = CASE ... ELSE col END
Dos casos en MySQLIF(cond, a, b)
  • La primera condición verdadera gana; las demás no se miran.
  • Sin ELSE, el resultado es NULL.
  • Todo CASE termina en END.

En la siguiente lección profundizarás en las funciones de agregación: COUNT, SUM, AVG, MIN y MAX.

Pon a prueba lo que has aprendido

[SQL] Ana García tiene 34 años. ¿Qué devuelve esta consulta en la columna t?
SELECT nombre,
       CASE
         WHEN edad >= 18 THEN 'Adulto'
         WHEN edad >= 30 THEN 'Treintañero'
         ELSE 'Menor'
       END AS t
FROM clientes
WHERE id = 1;

[SQL] El pedido 3 tiene un importe de 12.99. ¿Qué devuelve la columna tipo?
SELECT id,
       CASE
         WHEN importe > 100 THEN 'Grande'
         WHEN importe > 50 THEN 'Mediano'
       END AS tipo
FROM pedidos
WHERE id = 3;

[SQL] Con la tabla clientes del curso (dos clientes de Madrid), ¿qué devuelven las columnas a y b?
SELECT SUM(CASE WHEN ciudad = 'Madrid' THEN 1 ELSE 0 END) AS a,
       COUNT(CASE WHEN ciudad = 'Madrid' THEN 0 END) AS b
FROM clientes;

[SQL] ¿Qué pasa con la Mochila (categoría 'Montaña') al ejecutar este UPDATE en una tabla donde precio permite NULL?
UPDATE productos
SET precio = CASE categoria
               WHEN 'Escalada' THEN precio * 0.90
               WHEN 'Camping'  THEN precio * 0.85
             END;

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.