COUNT(DISTINCT col) responde a uma das perguntas analiticas mais comuns: quantos valores unicos existem em uma coluna. Parece trivial, mas em tabelas grandes e uma das agregacoes mais caras do SQL, e vale entender o porque.
Contagem basica de valores unicos
Um COUNT(*) conta linhas, enquanto COUNT(DISTINCT ...) primeiro remove duplicatas e depois conta o que sobra. Compare:
SELECT COUNT(*) FROM orders;
SELECT COUNT(DISTINCT user_id) FROM orders;
SELECT COUNT(DISTINCT country) FROM users;
Alguns comportamentos importantes:
NULL e ignorado: COUNT(DISTINCT col) nao trata NULL como um valor.
- Costuma ser combinado com
GROUP BY, por exemplo "compradores unicos por status do pedido":
SELECT status, COUNT(DISTINCT user_id) AS unique_buyers
FROM orders
GROUP BY status;
Por que e caro em escala
COUNT(*) pode se apoiar em estatisticas e indices e as vezes quase nao le dados. COUNT(DISTINCT) nao consegue: para descartar duplicatas o motor precisa materializar todos os valores e ordena-los ou carrega-los em uma tabela hash.
- Sem atalho por indice: ate um indice sobre
user_id nao guarda uma contagem pronta de valores unicos.
- Memoria: com milhoes de valores unicos a tabela hash ultrapassa o
work_mem e o PostgreSQL recorre a uma ordenacao em disco.
- Varios
COUNT(DISTINCT) na mesma consulta sao calculados separadamente, cada um com sua propria estrutura:
SELECT
COUNT(DISTINCT user_id) AS buyers,
COUNT(DISTINCT country) AS countries
FROM orders o
JOIN users u ON u.id = o.user_id;
Pegadinha: no MySQL COUNT(DISTINCT a, b) funciona diretamente. No PostgreSQL essa sintaxe e invalida; e preciso envolver as colunas em uma tupla ROW(...) ou combina-las de outra forma.
DISTINCT multicoluna
Quando a "unicidade" e definida por um par de colunas, use uma tupla no PostgreSQL:
SELECT COUNT(DISTINCT (user_id, country)) AS uniq_pairs
FROM orders o
JOIN users u ON u.id = o.user_id;
Uma alternativa e uma subconsulta com GROUP BY, que costuma ser mais legivel e otimiza de forma mais previsivel:
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 com a concatenacao de strings (user_id || '-' || country): entradas distintas podem colidir na mesma string e gerar resultado errado. Uma tupla ou um GROUP BY e mais seguro.
DISTINCT dentro de outras agregacoes
DISTINCT nao se limita ao COUNT. Voce pode coloca-lo dentro de SUM, AVG, array_agg e string_agg para agregar apenas 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;
Lembre-se de que SUM(DISTINCT salary) raramente faz sentido para dinheiro: dois pagamentos distintos de 1000 se fundem em um. Use DISTINCT dentro de um agregado apenas quando realmente for preciso remover duplicatas.
Alternativas aproximadas: APPROX e HLL
Quando voce precisa de uma ordem de grandeza sobre bilhoes de linhas, um COUNT(DISTINCT) exato e exagero. O algoritmo HyperLogLog estima contagens unicas em memoria fixa (kilobytes) com erro de cerca de 1-2%.
- ClickHouse:
uniq() e uniqHLL12() sao aproximados; uniqExact() e o equivalente exato de COUNT(DISTINCT).
- BigQuery / Snowflake:
APPROX_COUNT_DISTINCT(col).
- PostgreSQL: o nucleo nao tem, mas a extensao
postgresql-hll adiciona.
SELECT uniq(user_id) AS approx_buyers FROM orders;
SELECT APPROX_COUNT_DISTINCT(user_id) AS approx_buyers FROM orders;
A grande vantagem do HLL e ser combinavel: esbocos parciais podem ser fundidos. Isso permite guardar esbocos diarios e derivar unicos mensais sem reprocessar os dados brutos, algo que um COUNT(DISTINCT) exato nao consegue. Use a contagem aproximada em dashboards e graficos de tendencia, e reserve a exata para faturamento e relatorios onde cada unidade precisa estar correta.
COUNT(DISTINCT col)responde a uma das perguntas analiticas mais comuns: quantos valores unicos existem em uma coluna. Parece trivial, mas em tabelas grandes e uma das agregacoes mais caras do SQL, e vale entender o porque.Contagem basica de valores unicos
Um
COUNT(*)conta linhas, enquantoCOUNT(DISTINCT ...)primeiro remove duplicatas e depois conta o que sobra. Compare:-- 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;Alguns comportamentos importantes:
NULLe ignorado:COUNT(DISTINCT col)nao trataNULLcomo um valor.GROUP BY, por exemplo "compradores unicos por status do pedido":SELECT status, COUNT(DISTINCT user_id) AS unique_buyers FROM orders GROUP BY status;Por que e caro em escala
COUNT(*)pode se apoiar em estatisticas e indices e as vezes quase nao le dados.COUNT(DISTINCT)nao consegue: para descartar duplicatas o motor precisa materializar todos os valores e ordena-los ou carrega-los em uma tabela hash.user_idnao guarda uma contagem pronta de valores unicos.work_meme o PostgreSQL recorre a uma ordenacao em disco.COUNT(DISTINCT)na mesma consulta sao calculados separadamente, cada um com sua propria estrutura:-- 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;Pegadinha: no MySQL
COUNT(DISTINCT a, b)funciona diretamente. No PostgreSQL essa sintaxe e invalida; e preciso envolver as colunas em uma tuplaROW(...)ou combina-las de outra forma.DISTINCT multicoluna
Quando a "unicidade" e definida por um par de colunas, use uma tupla no 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;Uma alternativa e uma subconsulta com
GROUP BY, que costuma ser mais legivel e otimiza de forma mais previsivel: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 com a concatenacao de strings (
user_id || '-' || country): entradas distintas podem colidir na mesma string e gerar resultado errado. Uma tupla ou umGROUP BYe mais seguro.DISTINCT dentro de outras agregacoes
DISTINCTnao se limita aoCOUNT. Voce pode coloca-lo dentro deSUM,AVG,array_aggestring_aggpara agregar apenas 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;Lembre-se de que
SUM(DISTINCT salary)raramente faz sentido para dinheiro: dois pagamentos distintos de 1000 se fundem em um. UseDISTINCTdentro de um agregado apenas quando realmente for preciso remover duplicatas.Alternativas aproximadas: APPROX e HLL
Quando voce precisa de uma ordem de grandeza sobre bilhoes de linhas, um
COUNT(DISTINCT)exato e exagero. O algoritmo HyperLogLog estima contagens unicas em memoria fixa (kilobytes) com erro de cerca de 1-2%.uniq()euniqHLL12()sao aproximados;uniqExact()e o equivalente exato deCOUNT(DISTINCT).APPROX_COUNT_DISTINCT(col).postgresql-hlladiciona.-- 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;A grande vantagem do HLL e ser combinavel: esbocos parciais podem ser fundidos. Isso permite guardar esbocos diarios e derivar unicos mensais sem reprocessar os dados brutos, algo que um
COUNT(DISTINCT)exato nao consegue. Use a contagem aproximada em dashboards e graficos de tendencia, e reserve a exata para faturamento e relatorios onde cada unidade precisa estar correta.