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;
economicosrango_mediopremium
152015

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_pedidospendientesprocesandoenviadoscompletadoscancelados
35042286518926

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;
mestotalcompletadoscanceladospct_cancelacion
2025-012820310.7
2025-02312326.5
2025-033527411.4
2025-04292126.9
2025-05332539.1
2025-06383012.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;
categoriaQ1Q2Q3Q4total_anual
Smartphones22252831106
Accesorios electrónicos1820222585
Portátiles1214111855
Ropa mujer101281444
Ropa hombre91071238

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_descuentosin_descuentodescuento_nulototal
1283050

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