
SQL: Lógica Condicional con la Expresión CASE
- Dacadev
- Programación
- 11 de junio de 2026
Tabla de Contenido
Cuando una consulta necesita clasificar registros, calcular valores derivados o protegerse de casos límite, la lógica condicional en SQL evita mover decisiones simples a la capa de aplicación. Después de trabajar con filtrado de datos con WHERE, el siguiente paso natural es usar la expresión CASE para transformar resultados directamente dentro del query.
Note
En este artículo analizaremos cómo usar CASE en SQL con condiciones múltiples, validaciones de existencia, protección contra división por cero, actualizaciones condicionales y manejo explícito de valores NULL.
La expresión CASE cumple un rol similar a if / else if / else o switch / case en lenguajes de programación. Su valor está en que opera dentro del motor de base de datos: puedes derivar columnas, etiquetar estados y condicionar cálculos sin postprocesar cada fila en tu backend.
Observemos un primer ejemplo directo. A partir de una columna active, convertimos 1 y 0 en etiquetas más legibles:
mysql> SELECT first_name, last_name,
-> CASE
-> WHEN active = 1 THEN 'ACTIVE'
-> ELSE 'INACTIVE'
-> END activity_type
-> FROM customer;
+-------------+--------------+---------------+
| first_name | last_name | activity_type |
+-------------+--------------+---------------+
| MARY | SMITH | ACTIVE |
| PATRICIA | JOHNSON | ACTIVE |
| LINDA | WILLIAMS | ACTIVE |
| BARBARA | JONES | ACTIVE |
| ELIZABETH | BROWN | ACTIVE |
| JENNIFER | DAVIS | ACTIVE |
...
| KENT | ARSENAULT | ACTIVE |
| TERRANCE | ROUSH | INACTIVE |
| RENE | MCALISTER | ACTIVE |
| EDUARDO | HIATT | ACTIVE |
| TERRENCE | GUNDERSON | ACTIVE |
| ENRIQUE | FORSYTHE | ACTIVE |
| FREDDIE | DUGGAN | ACTIVE |
| WADE | DELVALLE | ACTIVE |
| AUSTIN | CINTRON | ACTIVE |
+-------------+--------------+---------------+
599 rows in set (0.00 sec)
La expresión CASE
La expresión CASE evalúa una o varias condiciones y retorna un valor según la primera coincidencia encontrada. En SQL, CASE no es solo una herramienta cosmética; también permite modelar reglas de negocio dentro de consultas, reportes y operaciones UPDATE.
Existen dos variantes principales:
- Searched CASE: cada
WHENcontiene una condición completa. - Simple CASE: se evalúa una expresión base contra valores específicos.
Info
Si ninguna condición coincide y no defines ELSE, el resultado de CASE será NULL. Conviene declarar ELSE explícitamente cuando el consumidor del dato espera una categoría estable.
Searched CASE
El Searched CASE es la forma más flexible porque cada rama puede evaluar una condición distinta. Su estructura general es:
CASE
WHEN C1 THEN E1
WHEN C2 THEN E2
...
WHEN CN THEN EN
[ELSE ED]
END
En este patrón, C1, C2 y CN representan condiciones booleanas. Cuando una condición se cumple, CASE retorna su expresión asociada (E1, E2, EN) y deja de evaluar las siguientes ramas.
Analicemos una clasificación de categorías de películas:
CASE
WHEN category.name IN ('Children','Family','Sports','Animation')
THEN 'All Ages'
WHEN category.name = 'Horror'
THEN 'Adult'
WHEN category.name IN ('Music','Games')
THEN 'Teens'
ELSE 'Other'
END
Esta variante permite mezclar operadores como IN, =, comparaciones numéricas, subconsultas y funciones. También se puede usar para evitar cálculos innecesarios en ciertas filas.
Consideremos el siguiente caso: si un cliente está inactivo, queremos retornar 0; si está activo, contamos sus rentas:
mysql> SELECT c.first_name, c.last_name,
-> CASE
-> WHEN active = 0 THEN 0
-> ELSE
-> (SELECT count(*) FROM rental r
-> WHERE r.customer_id = c.customer_id)
-> END num_rentals
-> FROM customer c;
+-------------+--------------+-------------+
| first_name | last_name | num_rentals |
+-------------+--------------+-------------+
| MARY | SMITH | 32 |
| PATRICIA | JOHNSON | 27 |
| LINDA | WILLIAMS | 26 |
| BARBARA | JONES | 22 |
| ELIZABETH | BROWN | 38 |
| JENNIFER | DAVIS | 28 |
...
| TERRANCE | ROUSH | 0 |
| RENE | MCALISTER | 26 |
| EDUARDO | HIATT | 27 |
| TERRENCE | GUNDERSON | 30 |
| ENRIQUE | FORSYTHE | 28 |
| FREDDIE | DUGGAN | 25 |
| WADE | DELVALLE | 22 |
| AUSTIN | CINTRON | 19 |
+-------------+--------------+-------------+
599 rows in set (0.01 sec)
Simple CASE
El Simple CASE es útil cuando todas las ramas comparan la misma expresión contra valores concretos. En lugar de repetir category.name = ..., colocamos category.name una sola vez después de CASE.
CASE category.name
WHEN 'Children' THEN 'All Ages'
WHEN 'Family' THEN 'All Ages'
WHEN 'Sports' THEN 'All Ages'
WHEN 'Animation' THEN 'All Ages'
WHEN 'Horror' THEN 'Adult'
WHEN 'Music' THEN 'Teens'
WHEN 'Games' THEN 'Teens'
ELSE 'Other'
END
Esta forma es más compacta y legible cuando la decisión depende de una única columna o expresión. Su limitación es que no permite condiciones heterogéneas por rama; para eso conviene usar Searched CASE.
Tip
Usa Simple CASE cuando estás mapeando valores discretos de una misma columna. Usa Searched CASE cuando necesitas rangos, IN, EXISTS, comparaciones entre columnas o reglas más compuestas.
Comprobar la existencia
Uno de los usos más prácticos de CASE es combinarlo con EXISTS. Esta técnica permite crear columnas tipo bandera (Y / N, true / false, 1 / 0) según la existencia de filas relacionadas.
Observemos una consulta que marca si un actor participó en películas con ciertas clasificaciones:
mysql> SELECT a.first_name, a.last_name,
-> CASE
-> WHEN EXISTS (SELECT 1 FROM film_actor fa
-> INNER JOIN film f ON fa.film_id = f.film_id
-> WHERE fa.actor_id = a.actor_id
-> AND f.rating = 'G') THEN 'Y'
-> ELSE 'N'
-> END g_actor,
-> CASE
-> WHEN EXISTS (SELECT 1 FROM film_actor fa
-> INNER JOIN film f ON fa.film_id = f.film_id
-> WHERE fa.actor_id = a.actor_id
-> AND f.rating = 'PG') THEN 'Y'
-> ELSE 'N'
-> END pg_actor,
-> CASE
-> WHEN EXISTS (SELECT 1 FROM film_actor fa
-> INNER JOIN film f ON fa.film_id = f.film_id
-> WHERE fa.actor_id = a.actor_id
-> AND f.rating = 'NC-17') THEN 'Y'
-> ELSE 'N'
-> END nc17_actor
-> FROM actor a
-> WHERE a.last_name LIKE 'S%' OR a.first_name LIKE 'S%';
+------------+-------------+---------+----------+------------+
| first_name | last_name | g_actor | pg_actor | nc17_actor |
+------------+-------------+---------+----------+------------+
| JOE | SWANK | Y | Y | Y |
| SANDRA | KILMER | Y | Y | Y |
| CAMERON | STREEP | Y | Y | Y |
| SANDRA | PECK | Y | Y | Y |
| SISSY | SOBIESKI | Y | Y | N |
| NICK | STALLONE | Y | Y | Y |
| SEAN | WILLIAMS | Y | Y | Y |
| GROUCHO | SINATRA | Y | Y | Y |
| SCARLETT | DAMON | Y | Y | Y |
| SPENCER | PECK | Y | Y | Y |
| SEAN | GUINESS | Y | Y | Y |
| SPENCER | DEPP | Y | Y | Y |
| SUSAN | DAVIS | Y | Y | Y |
| SIDNEY | CROWE | Y | Y | Y |
| SYLVESTER | DERN | Y | Y | Y |
| SUSAN | DAVIS | Y | Y | Y |
| DAN | STREEP | Y | Y | Y |
| SALMA | NOLTE | Y | N | Y |
| SCARLETT | BENING | Y | Y | Y |
| JEFF | SILVERSTONE | Y | Y | Y |
| JOHN | SUVARI | Y | Y | Y |
| JAYNE | SILVERSTONE | Y | Y | Y |
+------------+-------------+---------+----------+------------+
22 rows in set (0.00 sec)
También podemos usar CASE con una subconsulta escalar. Aquí se clasifica la disponibilidad de una película según la cantidad de copias en inventario:
mysql> SELECT f.title,
-> CASE (SELECT count(*) FROM inventory i
-> WHERE i.film_id = f.film_id)
-> WHEN 0 THEN 'Out Of Stock'
-> WHEN 1 THEN 'Scarce'
-> WHEN 2 THEN 'Scarce'
-> WHEN 3 THEN 'Available'
-> WHEN 4 THEN 'Available'
-> ELSE 'Common'
-> END film_availability
-> FROM film f
-> ;
+-----------------------------+-------------------+
| title | film_availability |
+-----------------------------+-------------------+
| ACADEMY DINOSAUR | Common |
| ACE GOLDFINGER | Available |
| ADAPTATION HOLES | Available |
| AFFAIR PREJUDICE | Common |
| AFRICAN EGG | Available |
| AGENT TRUMAN | Common |
| AIRPLANE SIERRA | Common |
| AIRPORT POLLOCK | Available |
| ALABAMA DEVIL | Common |
| ALADDIN CALENDAR | Common |
| ALAMO VIDEOTAPE | Common |
| ALASKA PHANTOM | Common |
| ALI FOREVER | Available |
| ALICE FANTASIA | Out Of Stock |
...
| YOUNG LANGUAGE | Scarce |
| YOUTH KICK | Scarce |
| ZHIVAGO CORE | Scarce |
| ZOOLANDER FICTION | Common |
| ZORRO ARK | Common |
+-----------------------------+-------------------+
1000 rows in set (0.01 sec)
Errores de división por cero
La división por cero no está definida matemáticamente y los motores de base de datos deben tratarla como un caso especial. En MySQL, por ejemplo, una división directa entre cero puede retornar NULL:
mysql> SELECT 100 / 0;
+---------+
| 100 / 0 |
+---------+
| NULL |
+---------+
1 row in set (0.00 sec)
En reportes, un NULL inesperado puede contaminar cálculos posteriores o romper supuestos del consumidor. CASE permite controlar el denominador antes de ejecutar la división.
mysql> SELECT c.first_name, c.last_name,
-> sum(p.amount) tot_payment_amt,
-> count(p.amount) num_payments,
-> sum(p.amount) /
-> CASE WHEN count(p.amount) = 0 THEN 1
-> ELSE count(p.amount)
-> END avg_payment
-> FROM customer c
-> LEFT OUTER JOIN payment p
-> ON c.customer_id = p.customer_id
-> GROUP BY c.first_name, c.last_name;
+------------+------------+-----------------+--------------+-------------+
| first_name | last_name | tot_payment_amt | num_payments | avg_payment |
+------------+------------+-----------------+--------------+-------------+
| MARY | SMITH | 118.68 | 32 | 3.708750 |
| PATRICIA | JOHNSON | 128.73 | 27 | 4.767778 |
| LINDA | WILLIAMS | 135.74 | 26 | 5.220769 |
| BARBARA | JONES | 81.78 | 22 | 3.717273 |
| ELIZABETH | BROWN | 144.62 | 38 | 3.805789 |
...
| EDUARDO | HIATT | 130.73 | 27 | 4.841852 |
| TERRENCE | GUNDERSON | 117.70 | 30 | 3.923333 |
| ENRIQUE | FORSYTHE | 96.72 | 28 | 3.454286 |
| FREDDIE | DUGGAN | 99.75 | 25 | 3.990000 |
| WADE | DELVALLE | 83.78 | 22 | 3.808182 |
| AUSTIN | CINTRON | 83.81 | 19 | 4.411053 |
+------------+------------+-----------------+--------------+-------------+
599 rows in set (0.07 sec)
Warning
Esta técnica evita el error o el NULL, pero no siempre significa que dividir entre 1 sea semánticamente correcto. En métricas financieras o analíticas, valida si conviene retornar 0, NULL controlado o una etiqueta explícita.
Actualizaciones condicionales
CASE también puede usarse dentro de UPDATE. Esto permite modificar registros según reglas calculadas al momento de la escritura.
El siguiente ejemplo desactiva clientes activos si su última renta ocurrió hace 90 días o más; en caso contrario, mantiene el registro activo:
UPDATE customer
SET active =
CASE
WHEN 90 <= (SELECT datediff(now(), max(rental_date))
FROM rental r
WHERE r.customer_id = customer.customer_id)
THEN 0
ELSE 1
END
WHERE active = 1;
Esta aproximación concentra la regla en una sola sentencia SQL. Antes de ejecutar un UPDATE de este tipo en producción, conviene probar el mismo CASE en un SELECT, revisar cuántas filas cambiarán y ejecutar la operación dentro de una transacción si el motor lo permite.
Manejo de valores NULL
Los valores NULL aparecen con frecuencia cuando usamos LEFT OUTER JOIN, porque no siempre existe una fila relacionada. CASE nos permite reemplazarlos por un valor controlado para reportes o interfaces.
SELECT c.first_name, c.last_name,
CASE
WHEN a.address IS NULL THEN 'Unknown'
ELSE a.address
END address,
CASE
WHEN ct.city IS NULL THEN 'Unknown'
ELSE ct.city
END city,
CASE
WHEN cn.country IS NULL THEN 'Unknown'
ELSE cn.country
END country
FROM customer c
LEFT OUTER JOIN address a
ON c.address_id = a.address_id
LEFT OUTER JOIN city ct
ON a.city_id = ct.city_id
LEFT OUTER JOIN country cn
ON ct.country_id = cn.country_id;
En varios motores también existen funciones como COALESCE, útiles para este caso específico. Aun así, CASE sigue siendo preferible cuando la lógica depende de más de una condición o cuando el valor de salida no es simplemente el primer dato no nulo.
Cierre
La expresión CASE convierte SQL en una herramienta más expresiva para preparar datos cerca de donde viven. Con ella puedes clasificar filas, derivar columnas, controlar casos límite, ejecutar actualizaciones condicionales y normalizar valores faltantes.
Como regla práctica, usa CASE para reglas de presentación, clasificación y cálculo que pertenecen naturalmente al resultado de la consulta. Si la lógica empieza a depender de múltiples procesos externos o de reglas de dominio altamente cambiantes, evalúa moverla a una capa de aplicación o a una vista bien documentada.
En el siguiente artículo de la serie vamos a trabajar con agrupamiento y funciones agregadas, donde GROUP BY, COUNT, SUM y otras funciones nos permitirán resumir datos de manera consistente.


