COUNT DISTINCT
Cuando necesitas saber cuántos valores diferentes existen en una columna, COUNT(DISTINCT) es la herramienta adecuada. A diferencia de COUNT(*) que cuenta todas las filas, o COUNT(columna) que cuenta las filas no nulas, COUNT(DISTINCT columna) elimina los duplicados antes de contar. Es una función que aparece constantemente en análisis de datos: cuántos clientes distintos compraron, cuántas ciudades diferentes hay en la base de datos, cuántos productos únicos se vendieron en un período.
Sintaxis
COUNT(DISTINCT expresion)
COUNT(DISTINCT expresion1, expresion2, ...)La primera forma cuenta los valores únicos de una sola columna o expresión. La segunda forma, con múltiples expresiones, cuenta las combinaciones únicas de esos valores. En ambos casos, los valores NULL se excluyen del conteo.
Comportamiento básico
Veamos la diferencia entre las tres variantes de COUNT con un ejemplo sobre la tabla pedidos:
SELECT
COUNT(*) AS total_filas,
COUNT(cliente_id) AS filas_con_cliente,
COUNT(DISTINCT cliente_id) AS clientes_distintos
FROM pedidos;| total_filas | filas_con_cliente | clientes_distintos |
|---|---|---|
| 350 | 350 | 48 |
Hay 350 pedidos en total, todos tienen un cliente_id asignado, pero solo corresponden a 48 clientes diferentes. Esto significa que, en promedio, cada cliente ha realizado unos 7 pedidos. Con una sola consulta ya tienes una visión clara de la distribución.
Caso práctico: clientes únicos por período
Uno de los usos más habituales de COUNT(DISTINCT) es medir la cantidad de clientes únicos que compraron en cada mes:
SELECT
DATE_FORMAT(fecha_pedido, '%Y-%m') AS mes,
COUNT(*) AS total_pedidos,
COUNT(DISTINCT cliente_id) AS clientes_unicos
FROM pedidos
WHERE YEAR(fecha_pedido) = 2025
GROUP BY DATE_FORMAT(fecha_pedido, '%Y-%m')
ORDER BY mes;| mes | total_pedidos | clientes_unicos |
|---|---|---|
| 2025-01 | 28 | 18 |
| 2025-02 | 31 | 20 |
| 2025-03 | 35 | 22 |
| 2025-04 | 29 | 19 |
| 2025-05 | 33 | 21 |
| 2025-06 | 38 | 25 |
La diferencia entre total_pedidos y clientes_unicos te indica cuántos clientes son recurrentes en cada mes. Un mes con 38 pedidos y 25 clientes únicos tiene 13 pedidos de clientes que compraron más de una vez.
Caso práctico: productos únicos vendidos por categoría
Para analizar la variedad de productos vendidos en cada categoría:
SELECT
c.nombre AS categoria,
COUNT(DISTINCT dp.producto_id) AS productos_vendidos,
COUNT(*) AS lineas_de_venta
FROM detalle_pedidos dp
JOIN productos p ON dp.producto_id = p.id
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
ORDER BY productos_vendidos DESC;| categoria | productos_vendidos | lineas_de_venta |
|---|---|---|
| Smartphones | 11 | 187 |
| Portátiles | 7 | 94 |
| Accesorios electrónicos | 7 | 156 |
| Ropa hombre | 5 | 68 |
| Ropa mujer | 5 | 72 |
| Hogar | 4 | 45 |
Los accesorios electrónicos son interesantes: solo 7 productos distintos generan 156 líneas de venta, lo que indica alta rotación y recompra en esa categoría.
Caso práctico: COUNT DISTINCT con múltiples columnas
MySQL permite pasar varias columnas a COUNT(DISTINCT ...) para contar combinaciones únicas. Por ejemplo, si quieres saber cuántas combinaciones diferentes de cliente y producto existen (es decir, cuántos clientes distintos compraron cada producto al menos una vez):
SELECT
COUNT(DISTINCT cliente_id, producto_id) AS combinaciones_unicas
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id;| combinaciones_unicas |
|---|
| 312 |
De todas las líneas de detalle, hay 312 pares únicos de cliente-producto. Esto te dice que hay 312 relaciones de compra diferentes, sin importar cuántas veces un mismo cliente compró el mismo producto.
También puedes usarlo agrupado para ver cuántos productos distintos compró cada cliente:
SELECT
cl.nombre,
cl.apellidos,
COUNT(DISTINCT dp.producto_id) AS productos_diferentes
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
GROUP BY cl.id, cl.nombre, cl.apellidos
ORDER BY productos_diferentes DESC
LIMIT 5;| nombre | apellidos | productos_diferentes |
|---|---|---|
| María | García López | 14 |
| Carlos | Rodríguez Martín | 12 |
| Ana | Martínez Ruiz | 11 |
| Pedro | Sánchez Gómez | 9 |
| Laura | Fernández Díaz | 9 |
Manejo de NULL
COUNT(DISTINCT) ignora los valores NULL, al igual que COUNT(columna). Si una columna tiene valores nulos, no se cuentan como un valor distinto:
SELECT
COUNT(DISTINCT ciudad) AS ciudades_distintas,
COUNT(ciudad) AS filas_con_ciudad,
COUNT(*) AS total_clientes
FROM clientes;| ciudades_distintas | filas_con_ciudad | total_clientes |
|---|---|---|
| 15 | 45 | 50 |
Hay 50 clientes, 45 tienen ciudad registrada, y entre esas 45 filas hay 15 ciudades diferentes. Los 5 clientes sin ciudad se excluyen completamente del conteo DISTINCT.
Combinación con otras funciones
COUNT(DISTINCT) es especialmente útil dentro de HAVING para filtrar grupos por diversidad:
SELECT
DATE_FORMAT(p.fecha_pedido, '%Y-%m') AS mes,
COUNT(DISTINCT dp.producto_id) AS productos_vendidos
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
GROUP BY DATE_FORMAT(p.fecha_pedido, '%Y-%m')
HAVING COUNT(DISTINCT dp.producto_id) >= 20
ORDER BY mes;Esta consulta encuentra los meses donde se vendieron al menos 20 productos diferentes, lo cual puede indicar meses con campañas de marketing exitosas o temporadas altas.
Otra combinación frecuente es calcular el ratio entre valores totales y valores únicos para medir la repetición:
SELECT
c.nombre AS categoria,
COUNT(*) AS total_ventas,
COUNT(DISTINCT p.cliente_id) AS clientes_unicos,
ROUND(COUNT(*) / COUNT(DISTINCT p.cliente_id), 1) AS compras_por_cliente
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
GROUP BY c.nombre
ORDER BY compras_por_cliente DESC;| categoria | total_ventas | clientes_unicos | compras_por_cliente |
|---|---|---|---|
| Accesorios electrónicos | 156 | 32 | 4.9 |
| Smartphones | 187 | 40 | 4.7 |
| Ropa mujer | 72 | 22 | 3.3 |
| Portátiles | 94 | 30 | 3.1 |
El ratio compras_por_cliente revela qué categorías generan más recompra. Los accesorios electrónicos tienen la tasa más alta con casi 5 compras por cliente único.
Rendimiento con grandes volúmenes
COUNT(DISTINCT) es más costoso que COUNT(*) porque MySQL necesita mantener en memoria los valores únicos para detectar duplicados. En tablas con millones de registros y alta cardinalidad (muchos valores distintos), la consulta puede consumir bastante memoria. Si experimentas problemas de rendimiento, considera crear un índice sobre la columna que estás contando, ya que MySQL puede usar el índice para eliminar duplicados de forma más eficiente.
Practica con COUNT DISTINCT
Usa el editor para contar valores únicos:
En el siguiente artículo veremos COUNT IF para contar filas que cumplen una condición.
Escrito por Eduardo Lázaro
