COUNT(DISTINCT col) responde a una de las preguntas analiticas mas frecuentes: cuantos valores unicos hay en una columna. Suena trivial, pero en tablas grandes es una de las agregaciones mas caras de SQL, y conviene entender por que.
Conteo basico de valores unicos
Un COUNT(*) cuenta filas, mientras que COUNT(DISTINCT ...) primero elimina los duplicados y luego cuenta lo que queda. Compara:
SELECT COUNT(*) FROM orders;
SELECT COUNT(DISTINCT user_id) FROM orders;
SELECT COUNT(DISTINCT country) FROM users;
Algunos comportamientos que conviene conocer:
NULL se ignora: COUNT(DISTINCT col) no trata NULL como un valor.
- Suele combinarse con
GROUP BY, por ejemplo "compradores unicos por estado del pedido":
SELECT status, COUNT(DISTINCT user_id) AS unique_buyers
FROM orders
GROUP BY status;
Por que es caro a gran escala
COUNT(*) puede apoyarse en estadisticas e indices y a veces apenas lee datos. COUNT(DISTINCT) no puede: para descartar duplicados el motor debe materializar todos los valores y ordenarlos o cargarlos en una tabla hash.
- Sin atajo por indice: incluso un indice sobre
user_id no guarda un conteo listo de valores unicos.
- Memoria: con millones de valores unicos la tabla hash supera
work_mem y PostgreSQL recurre a un ordenamiento en disco.
- Varios
COUNT(DISTINCT) en una misma consulta se calculan por separado, cada uno con su propia estructura:
SELECT
COUNT(DISTINCT user_id) AS buyers,
COUNT(DISTINCT country) AS countries
FROM orders o
JOIN users u ON u.id = o.user_id;
Trampa: en MySQL COUNT(DISTINCT a, b) funciona directamente. En PostgreSQL esa sintaxis no es valida; hay que envolver las columnas en una tupla ROW(...) o combinarlas de otra forma.
DISTINCT multicolumna
Cuando la "unicidad" la define un par de columnas, usa una tupla en PostgreSQL:
SELECT COUNT(DISTINCT (user_id, country)) AS uniq_pairs
FROM orders o
JOIN users u ON u.id = o.user_id;
Una alternativa es una subconsulta con GROUP BY, que suele leerse mejor y se optimiza de forma mas predecible:
SELECT COUNT(*) AS uniq_pairs
FROM (
SELECT DISTINCT user_id, country
FROM orders o
JOIN users u ON u.id = o.user_id
) t;
Cuidado con la concatenacion de cadenas (user_id || '-' || country): entradas distintas pueden colisionar en la misma cadena y dar un resultado incorrecto. Una tupla o un GROUP BY es mas seguro.
DISTINCT dentro de otros agregados
DISTINCT no se limita a COUNT. Puedes ponerlo dentro de SUM, AVG, array_agg y string_agg para agregar solo valores unicos:
SELECT dept, SUM(DISTINCT salary) AS sum_unique_salaries
FROM employees
GROUP BY dept;
SELECT manager_id, string_agg(DISTINCT dept, ', ') AS depts
FROM employees
GROUP BY manager_id;
Recuerda que SUM(DISTINCT salary) rara vez tiene sentido para dinero: dos pagos distintos de 1000 se colapsan en uno. Usa DISTINCT dentro de un agregado solo cuando de verdad haya que eliminar duplicados.
Alternativas aproximadas: APPROX y HLL
Cuando necesitas un orden de magnitud sobre miles de millones de filas, un COUNT(DISTINCT) exacto es excesivo. El algoritmo HyperLogLog estima conteos unicos en memoria fija (kilobytes) con un error de alrededor del 1-2%.
- ClickHouse:
uniq() y uniqHLL12() son aproximados; uniqExact() es el equivalente exacto de COUNT(DISTINCT).
- BigQuery / Snowflake:
APPROX_COUNT_DISTINCT(col).
- PostgreSQL: el nucleo no lo trae, pero la extension
postgresql-hll lo anade.
SELECT uniq(user_id) AS approx_buyers FROM orders;
SELECT APPROX_COUNT_DISTINCT(user_id) AS approx_buyers FROM orders;
La gran ventaja de HLL es que es combinable: los bocetos parciales se pueden fusionar. Eso permite guardar bocetos diarios y derivar unicos mensuales sin reprocesar los datos crudos, algo que un COUNT(DISTINCT) exacto no puede hacer. Usa el conteo aproximado para paneles y graficas de tendencia, y reserva el exacto para facturacion e informes donde cada unidad debe ser correcta.
COUNT(DISTINCT col)responde a una de las preguntas analiticas mas frecuentes: cuantos valores unicos hay en una columna. Suena trivial, pero en tablas grandes es una de las agregaciones mas caras de SQL, y conviene entender por que.Conteo basico de valores unicos
Un
COUNT(*)cuenta filas, mientras queCOUNT(DISTINCT ...)primero elimina los duplicados y luego cuenta lo que queda. Compara:-- Total order rows SELECT COUNT(*) FROM orders; -- How many distinct customers actually ordered SELECT COUNT(DISTINCT user_id) FROM orders; -- Distinct countries among registered users SELECT COUNT(DISTINCT country) FROM users;Algunos comportamientos que conviene conocer:
NULLse ignora:COUNT(DISTINCT col)no trataNULLcomo un valor.GROUP BY, por ejemplo "compradores unicos por estado del pedido":SELECT status, COUNT(DISTINCT user_id) AS unique_buyers FROM orders GROUP BY status;Por que es caro a gran escala
COUNT(*)puede apoyarse en estadisticas e indices y a veces apenas lee datos.COUNT(DISTINCT)no puede: para descartar duplicados el motor debe materializar todos los valores y ordenarlos o cargarlos en una tabla hash.user_idno guarda un conteo listo de valores unicos.work_memy PostgreSQL recurre a un ordenamiento en disco.COUNT(DISTINCT)en una misma consulta se calculan por separado, cada uno con su propia estructura:-- Each DISTINCT is computed independently -> heavy SELECT COUNT(DISTINCT user_id) AS buyers, COUNT(DISTINCT country) AS countries FROM orders o JOIN users u ON u.id = o.user_id;Trampa: en MySQL
COUNT(DISTINCT a, b)funciona directamente. En PostgreSQL esa sintaxis no es valida; hay que envolver las columnas en una tuplaROW(...)o combinarlas de otra forma.DISTINCT multicolumna
Cuando la "unicidad" la define un par de columnas, usa una tupla en PostgreSQL:
-- Unique (user, country) pairs in PostgreSQL SELECT COUNT(DISTINCT (user_id, country)) AS uniq_pairs FROM orders o JOIN users u ON u.id = o.user_id;Una alternativa es una subconsulta con
GROUP BY, que suele leerse mejor y se optimiza de forma mas predecible:SELECT COUNT(*) AS uniq_pairs FROM ( SELECT DISTINCT user_id, country FROM orders o JOIN users u ON u.id = o.user_id ) t;Cuidado con la concatenacion de cadenas (
user_id || '-' || country): entradas distintas pueden colisionar en la misma cadena y dar un resultado incorrecto. Una tupla o unGROUP BYes mas seguro.DISTINCT dentro de otros agregados
DISTINCTno se limita aCOUNT. Puedes ponerlo dentro deSUM,AVG,array_aggystring_aggpara agregar solo valores unicos:-- Sum of distinct salaries per department (dedupes equal salaries first) SELECT dept, SUM(DISTINCT salary) AS sum_unique_salaries FROM employees GROUP BY dept; -- Comma-separated list of distinct departments per manager SELECT manager_id, string_agg(DISTINCT dept, ', ') AS depts FROM employees GROUP BY manager_id;Recuerda que
SUM(DISTINCT salary)rara vez tiene sentido para dinero: dos pagos distintos de 1000 se colapsan en uno. UsaDISTINCTdentro de un agregado solo cuando de verdad haya que eliminar duplicados.Alternativas aproximadas: APPROX y HLL
Cuando necesitas un orden de magnitud sobre miles de millones de filas, un
COUNT(DISTINCT)exacto es excesivo. El algoritmo HyperLogLog estima conteos unicos en memoria fija (kilobytes) con un error de alrededor del 1-2%.uniq()yuniqHLL12()son aproximados;uniqExact()es el equivalente exacto deCOUNT(DISTINCT).APPROX_COUNT_DISTINCT(col).postgresql-hlllo anade.-- ClickHouse: fast approximate distinct SELECT uniq(user_id) AS approx_buyers FROM orders; -- BigQuery / Snowflake style SELECT APPROX_COUNT_DISTINCT(user_id) AS approx_buyers FROM orders;La gran ventaja de HLL es que es combinable: los bocetos parciales se pueden fusionar. Eso permite guardar bocetos diarios y derivar unicos mensuales sin reprocesar los datos crudos, algo que un
COUNT(DISTINCT)exacto no puede hacer. Usa el conteo aproximado para paneles y graficas de tendencia, y reserva el exacto para facturacion e informes donde cada unidad debe ser correcta.