Agregación condicional: FILTER y CASE
IntermedioPara contar o sumar solo las filas que cumplen una condición sin separar la consulta en varias, se pone la condición dentro del agregado. PostgreSQL tiene count(*) FILTER (WHERE ...); la forma portable es sum(CASE WHEN ... THEN 1 ELSE 0 END). Así cada condición queda en su propia columna, con una fila por grupo.
Sintaxis
SELECT
grupo,
count(*) FILTER (WHERE condicion) AS con_filter,
sum(CASE WHEN condicion THEN 1 ELSE 0 END) AS con_case
FROM tabla
GROUP BY grupo;Ejemplo
SELECT
c.name AS ciudad,
count(*) AS pedidos,
count(*) FILTER (WHERE o.status = 'delivered') AS entregados,
count(*) FILTER (WHERE o.status = 'cancelled') AS cancelados,
sum(CASE WHEN o.payment_method = 'cash' THEN 1 ELSE 0 END) AS en_efectivo,
round(100.0 * count(*) FILTER (WHERE o.status = 'cancelled') / count(*), 1) AS pct_cancelados
FROM orders AS o
INNER JOIN restaurants AS r ON r.id = o.restaurant_id
INNER JOIN cities AS c ON c.id = r.city_id
WHERE o.placed_at >= '2025-08-01'
AND o.placed_at < '2025-09-01'
GROUP BY c.name
ORDER BY pedidos DESC, ciudad;Resultado
| ciudad | pedidos | entregados | cancelados | en_efectivo | pct_cancelados |
|---|---|---|---|---|---|
| Ciudad de México | 324 | 293 | 31 | 50 | 9.6 |
| Buenos Aires | 294 | 271 | 23 | 44 | 7.8 |
| Bogotá | 158 | 148 | 10 | 27 | 6.3 |
| Lima | 145 | 135 | 10 | 25 | 6.9 |
| Santiago | 109 | 101 | 8 | 19 | 7.3 |
| Guadalajara | 101 | 94 | 7 | 12 | 6.9 |
| Córdoba | 95 | 88 | 7 | 19 | 7.4 |
| Montevideo | 54 | 49 | 5 | 8 | 9.3 |
Cómo leerlo
Una fila por ciudad y una columna por condición: es un pivot (pasar valores de filas a columnas) hecho a mano. FILTER y CASE dan el mismo resultado; se muestran los dos. La tasa usa 100.0 para que la división no sea entera. Montevideo cancela casi tanto como Ciudad de México en proporción, aunque tenga seis veces menos pedidos.
Error común
Así no
SELECT c.name AS ciudad, count(*) AS cancelados, count(*) AS pedidos
FROM orders AS o
INNER JOIN restaurants AS r ON r.id = o.restaurant_id
INNER JOIN cities AS c ON c.id = r.city_id
WHERE o.status = 'cancelled'
GROUP BY c.name;Filtrar la condición en el WHERE cuando también se necesita el total. El WHERE descarta los pedidos no cancelados antes de agrupar, así que pedidos cuenta lo mismo que cancelados y cualquier tasa daría 100 %. La condición va dentro del agregado. Otro error común: CASE WHEN ... THEN 1 END sin ELSE 0 dentro de sum da NULL (no 0) en los grupos sin casos.
En otros motores: PostgreSQL, BigQuery, MySQL
PostgreSQL
FILTER (WHERE ...)funciona con cualquier agregado:sum(total) FILTER (WHERE ...),avg(...) FILTER (...). MySQL, SQL Server y Oracle no lo aceptan: ahí se usaCASE. BigQuery y Snowflake tienen funciones propias.BigQuery
BigQuery tiene
COUNTIF(condicion). En Snowflake la función equivalente se llamaCOUNT_IF.SELECT city, COUNTIF(status = 'cancelled') AS cancelados FROM orders GROUP BY city;MySQL
Una comparación vale 1 o 0, así que
SUM(status = 'cancelled')cuenta los casos. No es portable: en PostgreSQLsumno acepta booleanos.
Estos fragmentos no se ejecutan en el curso: se muestran como referencia de cada motor.