COUNT IF
MySQL no tiene una función llamada COUNT_IF como otros sistemas de bases de datos, pero puedes lograr exactamente el mismo resultado combinando COUNT con IF o con SUM y una condición booleana. Este patrón te permite contar filas que cumplen distintas condiciones dentro de una misma consulta, evitando tener que escribir múltiples consultas con WHERE o subconsultas separadas.
Sintaxis
Existen dos patrones equivalentes para contar condicionalmente:
-- Patrón 1: COUNT con IF
COUNT(IF(condicion, 1, NULL))
-- Patrón 2: SUM con condición booleana
SUM(condicion)
-- Patrón 3: SUM con CASE
SUM(CASE WHEN condicion THEN 1 ELSE 0 END)El primer patrón usa IF para devolver 1 cuando la condición se cumple y NULL cuando no. Como COUNT ignora los NULL, solo cuenta las filas que cumplen la condición. El segundo patrón aprovecha que MySQL trata TRUE como 1 y FALSE como 0, así que SUM(condicion) suma unos y ceros, dando el mismo resultado. El tercer patrón con CASE es más explícito y portable a otros sistemas SQL.
Comportamiento básico
Veamos los tres patrones en acción para contar productos según su rango de precio:
SELECT
COUNT(IF(precio < 100, 1, NULL)) AS economicos,
SUM(precio >= 100 AND precio < 500) AS rango_medio,
SUM(CASE WHEN precio >= 500 THEN 1 ELSE 0 END) AS premium
FROM productos;| economicos | rango_medio | premium |
|---|---|---|
| 15 | 20 | 15 |
Las tres columnas usan técnicas diferentes pero el principio es el mismo: evaluar una condición por cada fila y acumular el resultado. En la práctica, los tres patrones son intercambiables y la elección es cuestión de preferencia. El patrón SUM(condicion) es el más conciso, mientras que SUM(CASE WHEN...) es el más legible para otros desarrolladores que quizá no conozcan la conversión implícita de booleanos en MySQL.
Caso práctico: resumen de estados de pedidos
Imagina que necesitas un informe que muestre cuántos pedidos hay en cada estado, pero en columnas en lugar de filas. Este es el escenario clásico para COUNT IF:
SELECT
COUNT(*) AS total_pedidos,
SUM(estado = 'pendiente') AS pendientes,
SUM(estado = 'procesando') AS procesando,
SUM(estado = 'enviado') AS enviados,
SUM(estado = 'completado') AS completados,
SUM(estado = 'cancelado') AS cancelados
FROM pedidos;| total_pedidos | pendientes | procesando | enviados | completados | cancelados |
|---|---|---|---|---|---|
| 350 | 42 | 28 | 65 | 189 | 26 |
Con una sola consulta obtienes el desglose completo. Sin este patrón, necesitarías cinco consultas separadas con WHERE o una consulta con GROUP BY estado que devolvería los datos en filas, requiriendo que la aplicación los transforme.
Caso práctico: análisis mensual con conteo condicional
Donde el patrón COUNT IF realmente brilla es en reportes que combinan GROUP BY con conteo condicional. Veamos un análisis mensual de pedidos:
SELECT
DATE_FORMAT(fecha_pedido, '%Y-%m') AS mes,
COUNT(*) AS total,
SUM(estado = 'completado') AS completados,
SUM(estado = 'cancelado') AS cancelados,
ROUND(SUM(estado = 'cancelado') / COUNT(*) * 100, 1) AS pct_cancelacion
FROM pedidos
WHERE YEAR(fecha_pedido) = 2025
GROUP BY DATE_FORMAT(fecha_pedido, '%Y-%m')
ORDER BY mes;| mes | total | completados | cancelados | pct_cancelacion |
|---|---|---|---|---|
| 2025-01 | 28 | 20 | 3 | 10.7 |
| 2025-02 | 31 | 23 | 2 | 6.5 |
| 2025-03 | 35 | 27 | 4 | 11.4 |
| 2025-04 | 29 | 21 | 2 | 6.9 |
| 2025-05 | 33 | 25 | 3 | 9.1 |
| 2025-06 | 38 | 30 | 1 | 2.6 |
La columna pct_cancelacion divide el conteo de cancelados entre el total y multiplica por 100 para obtener el porcentaje. Observa que junio tuvo la menor tasa de cancelación con un 2.6%.
Caso práctico: tabla pivote de ventas por categoría y trimestre
El patrón COUNT IF es la base de las consultas pivote en MySQL. Imagina que quieres ver cuántas ventas tuvo cada categoría en cada trimestre:
SELECT
c.nombre AS categoria,
SUM(QUARTER(p.fecha_pedido) = 1) AS Q1,
SUM(QUARTER(p.fecha_pedido) = 2) AS Q2,
SUM(QUARTER(p.fecha_pedido) = 3) AS Q3,
SUM(QUARTER(p.fecha_pedido) = 4) AS Q4,
COUNT(*) AS total_anual
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
WHERE YEAR(p.fecha_pedido) = 2025
GROUP BY c.nombre
ORDER BY total_anual DESC;| categoria | Q1 | Q2 | Q3 | Q4 | total_anual |
|---|---|---|---|---|---|
| Smartphones | 22 | 25 | 28 | 31 | 106 |
| Accesorios electrónicos | 18 | 20 | 22 | 25 | 85 |
| Portátiles | 12 | 14 | 11 | 18 | 55 |
| Ropa mujer | 10 | 12 | 8 | 14 | 44 |
| Ropa hombre | 9 | 10 | 7 | 12 | 38 |
Este formato de tabla pivote es mucho más fácil de leer en un informe que una lista con filas separadas para cada combinación de categoría y trimestre.
Manejo de NULL
Cuando usas el patrón SUM(condicion), ten en cuenta que si la columna evaluada contiene NULL, la condición se evalúa como NULL (no como FALSE). Esto generalmente no causa problemas porque SUM ignora los NULL, pero conviene ser explícito:
SELECT
SUM(descuento > 0) AS con_descuento,
SUM(descuento = 0) AS sin_descuento,
SUM(descuento IS NULL) AS descuento_nulo,
COUNT(*) AS total
FROM productos;| con_descuento | sin_descuento | descuento_nulo | total |
|---|---|---|---|
| 12 | 8 | 30 | 50 |
Observa que SUM(descuento > 0) devuelve 12, SUM(descuento = 0) devuelve 8 y SUM(descuento IS NULL) devuelve 30. La suma de los tres es 50, el total de productos. Si no cuentas los NULL explícitamente, los 30 productos sin descuento asignado se perderían del análisis.
Combinación con otras funciones
El patrón COUNT IF se combina naturalmente con otras funciones de agregación para crear informes completos:
SELECT
DATE_FORMAT(p.fecha_pedido, '%Y-%m') AS mes,
COUNT(DISTINCT p.id) AS total_pedidos,
SUM(p.estado = 'completado') AS completados,
ROUND(AVG(dp.cantidad * dp.precio_unitario), 2) AS ticket_medio,
SUM(CASE WHEN dp.cantidad * dp.precio_unitario > 200 THEN 1 ELSE 0 END) AS ventas_altas
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
WHERE YEAR(p.fecha_pedido) = 2025
GROUP BY DATE_FORMAT(p.fecha_pedido, '%Y-%m')
ORDER BY mes;En esta consulta combinamos COUNT(DISTINCT) para pedidos únicos, SUM(condicion) para conteo condicional, AVG para el ticket medio y SUM(CASE WHEN...) para otro conteo condicional, todo en la misma consulta.
En el siguiente artículo veremos SUM para sumar valores.
Escrito por Eduardo Lázaro
