DISTINCT: mantendo só o que é único
O que você vai aprender
- remover repetições da saída com
SELECT DISTINCT - entender que a unicidade não é avaliada por uma única coluna «principal», e sim pela combinação inteira de colunas escolhidas
- obter um dicionário de valores: quais categorias, status, cidades ou outras variantes realmente aparecem na tabela
- distinguir as tarefas do
DISTINCTdas tarefas doGROUP BY - entender que o
DISTINCT, sozinho, não ordena o resultado - explicar por que o
DISTINCTpode sair caro em tabelas grandes - reconhecer o antipadrão: um
DISTINCTacrescentado só para mascarar duplicatas depois de umJOIN
Só os valores únicos
No grande salão do depósito, cada som tem eco. O mesmo status se repete em milhares de pedidos. A mesma categoria atravessa centenas de itens. A mesma cidade acende no perfil de vários usuários.
Quando você precisa de todo o fluxo de dados, as repetições são normais.
Por exemplo, se você olha os pedidos, o status paid pode aparecer muitas vezes:
SELECT id, status
FROM orders;
O resultado pode ser assim:
| id | status |
|---|---|
| 1 | paid |
| 2 | paid |
| 3 | pending |
| 4 | cancelled |
| 5 | paid |
| 6 | pending |
Aqui cada linha é um pedido separado. Os status repetidos não são erro. Vários pedidos simplesmente têm o mesmo status.
Mas às vezes você não precisa do fluxo de pedidos, e sim de um pequeno dicionário:
Quais status existem nesta tabela, afinal?
Nesse caso não precisamos de todas as repetições de paid e pending. Basta manter cada valor uma única vez.
Para isso usamos o DISTINCT:
SELECT DISTINCT status
FROM orders;
Resultado:
| status |
|---|
| paid |
| pending |
| cancelled |
O DISTINCT remove as linhas repetidas do resultado da consulta.
Ele não altera os dados da tabela.
Ele não apaga linhas do banco.
Ele só limpa as duplicatas da saída.
QUERY: O eco do arquivo nem sempre é ruído. Às vezes ele mostra a frequência dos acontecimentos. Mas, se o que você quer é um dicionário e não um coral de repetições, ligue o silenciador:
DISTINCT.

DISTINCT silencia o eco do arquivo: milhares de repetições se condensam em um pequeno dicionário de valores únicos.Onde se escreve o DISTINCT
DISTINCT vem logo depois do SELECT:
SELECT DISTINCT status
FROM orders;
Essa posição importa.
Assim não:
SELECT status DISTINCT
FROM orders;
E assim também não:
SELECT status
FROM orders
DISTINCT;
A forma correta é:
SELECT DISTINCT colunas
FROM tabela;
Por exemplo, um dicionário das categorias dos itens:
SELECT DISTINCT category
FROM products;
Um dicionário das cidades dos usuários:
SELECT DISTINCT city
FROM users;
Um dicionário dos status dos pedidos:
SELECT DISTINCT status
FROM orders;
Em todas essas consultas a ideia é a mesma:
Mostre quais valores aparecem, sem repetições.
DISTINCT compara as linhas pela combinação inteira das colunas selecionadas: cada combinação permanece no resultado exatamente uma vez.orders, cada um uma vez. Entre eles está aquele mesmo pending, congelado para sempre.DISTINCT em uma única coluna
O caso mais simples é escolher os valores únicos de uma coluna.
Por exemplo, a tabela products:
| id | name | category |
|---|---|---|
| 1 | Ração "Atum Lunar" | Ração |
| 2 | Brinquedo "Rato Laser" | Brinquedos |
| 3 | Livro "SQL para catonautas" | Livros |
| 4 | Tigela antigravitacional | Acessórios |
| 5 | Livro "Memória da velha Terra" | Livros |
| 6 | Brinquedo "Rato Interceptador" | Brinquedos |
Se você escrever uma consulta comum:
SELECT category
FROM products;
o resultado mostra a categoria de cada linha:
| category |
|---|
| Ração |
| Brinquedos |
| Livros |
| Acessórios |
| Livros |
| Brinquedos |
Mas se você precisa da lista de categorias sem repetições:
SELECT DISTINCT category
FROM products;
o resultado fica assim:
| category |
|---|
| Ração |
| Brinquedos |
| Livros |
| Acessórios |
DISTINCT não escolhe "o primeiro item da categoria".
Ele não agrupa itens para fazer contas.
Ele apenas responde a uma pergunta:
Quais valores diferentes existem nesta coluna?
DISTINCT em várias colunas
O detalhe principal desta aula:
a unicidade é avaliada sobre a linha inteira do resultado.
Se você escolhe uma coluna, o DISTINCT remove as repetições dessa coluna.
SELECT DISTINCT category
FROM products;
Mas se você escolhe duas colunas:
SELECT DISTINCT category, status
FROM products;
o DISTINCT passa a procurar pares únicos:
category + status
Por exemplo, considere esta tabela:
| id | category | status |
|---|---|---|
| 1 | Livros | active |
| 2 | Livros | active |
| 3 | Livros | archived |
| 4 | Brinquedos | active |
| 5 | Brinquedos | active |
| 6 | Brinquedos | archived |
A consulta:
SELECT DISTINCT category
FROM products;
devolve:
| category |
|---|
| Livros |
| Brinquedos |
Já a consulta:
SELECT DISTINCT category, status
FROM products;
devolve:
| category | status |
|---|---|
| Livros | active |
| Livros | archived |
| Brinquedos | active |
| Brinquedos | archived |
Por quê?
Porque agora a unicidade não é avaliada só por category, e sim pelo par:
category + status
As linhas:
Livros + active
Livros + archived
são diferentes para o SQL, mesmo tendo a mesma categoria.
Por isso DISTINCT não significa "torne a primeira coluna única". Significa:
Remova do resultado as linhas repetidas por inteiro.
Como o DISTINCT trata o NULL
NULL significa ausência de valor, mas, para o DISTINCT, várias linhas com NULL na mesma coluna contam como repetição.
Por exemplo, a tabela users:
| id | name | city |
|---|---|---|
| 1 | Anna | Moscou |
| 2 | Boris | |
| 3 | Vera | Kazan |
| 4 | Gleb | NULL |
| 5 | Dana | Moscou |
A consulta:
SELECT DISTINCT city
FROM users;
devolve um conjunto parecido com este:
| city |
|---|
| Moscou |
| Kazan |
| NULL |
As duas linhas com NULL não aparecem duas vezes. No dicionário de cidades fica uma única linha NULL.
Isso não contradiz a aula anterior sobre NULL.
Em uma condição WHERE, comparar com NULL usando = dá UNKNOWN. Mas o DISTINCT resolve outro problema: ele remove as repetições de um resultado já pronto. Na deduplicação, vários valores ausentes na mesma coluna se juntam em uma linha só.
Se você não quer ver NULL no dicionário, acrescente um filtro:
SELECT DISTINCT city
FROM users
WHERE city IS NOT NULL
ORDER BY city;
Assim o resultado mostra apenas as cidades preenchidas.
O DISTINCT não ordena o resultado
DISTINCT remove as repetições, mas não define a ordem das linhas.
Por exemplo:
SELECT DISTINCT status
FROM orders;
pode devolver:
| status |
|---|
| pending |
| paid |
| cancelled |
E, em outra situação, a ordem pode ser diferente.
Isso é normal: uma tabela SQL não é obrigada a devolver as linhas em uma ordem "natural" se você não pediu a ordenação explicitamente.
Se você quer uma lista caprichada, acrescente o ORDER BY:
SELECT DISTINCT status
FROM orders
ORDER BY status;
Agora a consulta diz duas coisas separadas:
SELECT DISTINCT status
— remova as repetições;
ORDER BY status
— ordene o resultado por status.
Não pense que o DISTINCT "coloca ordem" sozinho. Ele silencia as duplicatas, não ordena.
DISTINCT vs. GROUP BY
Para um dicionário simples de valores, estas duas consultas podem dar o mesmo resultado:
SELECT DISTINCT category
FROM products;
e:
SELECT category
FROM products
GROUP BY category;
As duas retornam a lista de categorias sem repetições.
Mas o sentido de cada uma é diferente.
O DISTINCT responde à pergunta:
Quais valores existem?
O GROUP BY responde a outra:
Como dividir as linhas em grupos para depois calcular algo em cada um deles?
Por exemplo, se você só precisa da lista de categorias, basta o DISTINCT:
SELECT DISTINCT category
FROM products;
Já se você precisa saber quantos produtos há em cada categoria, aí entra o GROUP BY:
SELECT category, COUNT(*) AS products_count
FROM products
GROUP BY category;
O resultado:
| category | products_count |
|---|---|
| Acessórios | 5 |
| Brinquedos | 8 |
| Livros | 4 |
| Ração | 12 |
Aqui o GROUP BY faz mais do que remover repetições. Ele reúne as linhas em grupos para que a função de agregação COUNT(*) conte quantas linhas há dentro de cada grupo.
Por isso, a regra prática é esta:
Se você precisa de um dicionário de valores, use o DISTINCT.
SELECT DISTINCT category
FROM products;
Se você precisa calcular algo por grupo, use o GROUP BY.
SELECT category, COUNT(*)
FROM products
GROUP BY category;
O preço do silêncio
DISTINCT parece uma palavrinha pequena, mas o banco faz um trabalho real por trás dela.
Para remover as repetições, o SGBD precisa descobrir quais linhas do resultado são iguais. Para isso, ele precisa comparar as linhas entre si. Dependendo da situação, o banco pode:
- ordenar o resultado e remover as repetições que ficarem lado a lado;
- construir uma tabela hash de valores únicos;
- aproveitar um índice adequado, se ele ajudar.
Em uma tabela pequena, é quase impossível notar a diferença.
Por exemplo, se products tem só 20 linhas, a consulta:
SELECT DISTINCT category
FROM products;
vai rodar rápido.
Mas se a tabela tem milhões de linhas e você seleciona muitas colunas, a deduplicação pode virar uma fatia considerável do custo da consulta.
Uma consulta como esta sai especialmente cara:
SELECT DISTINCT *
FROM orders;
Aqui o banco precisa comparar linhas inteiras, em todas as colunas selecionadas. Quanto mais linhas e mais colunas, mais trabalho.
Por isso não vale a pena acrescentar DISTINCT “por via das dúvidas”.
Uma boa pergunta antes de usá-lo:
Que repetições exatamente eu quero remover, e por que elas apareceram?
Se a resposta for:
Preciso de um dicionário de status.
então o DISTINCT é a ferramenta certa.
SELECT DISTINCT status
FROM orders;
Se a resposta for:
Depois de juntar as tabelas, apareceram duplicatas estranhas, então eu acrescentei DISTINCT.
isso já é um sinal de alerta. Talvez o problema não esteja no resultado, e sim na lógica da junção.
DISTINCT depois do JOIN: quando ele mascara um problema
O cenário mais perigoso é acrescentar DISTINCT para “consertar” as duplicatas que aparecem depois de um JOIN.
Ainda não estudamos as junções em detalhe, mas vale guardar a ideia desde já.
Suponha que existam usuários e pedidos.
Um mesmo usuário pode fazer muitos pedidos.
Se você juntar usuários com pedidos, a linha do usuário se multiplica: uma linha para cada pedido dele.
Por exemplo:
| user_id | name | order_id |
|---|---|---|
| 1 | Anna | 101 |
| 1 | Anna | 102 |
| 1 | Anna | 103 |
| 2 | Boris | 104 |
Se você quer apenas os usuários, vai ver a Anna três vezes.
Às vezes, nessa situação, a pessoa escreve:
SELECT DISTINCT users.id, users.name
FROM users
JOIN orders ON orders.user_id = users.id;
E, à primeira vista, o resultado fica “bonito”:
| id | name |
|---|---|
| 1 | Anna |
| 2 | Boris |
Mas é importante entender: aqui o DISTINCT não explicou por que a Anna se multiplicou. Ele apenas eliminou as linhas repetidas depois da junção.
Às vezes isso é aceitável, se você quer mesmo a lista de usuários que têm pelo menos um pedido.
Mas se o DISTINCT foi acrescentado só porque “sem ele aparecem duplicatas, sei lá por quê”, isso é um mau cheiro na consulta.
A abordagem correta é entender a natureza dos dados:
- qual é a relação entre as tabelas: um para um ou um para muitos;
- por que uma linha da esquerda corresponde a várias linhas da direita;
- se você realmente precisa da lista de usuários únicos;
- ou se precisa contar os pedidos;
- ou se precisa escolher um pedido específico;
- ou se precisa mudar a condição da junção.
DISTINCT depois de um JOIN pode ser uma ferramenta legítima, mas não deve virar um curativo para um problema que você não entendeu.
QUERY: Se o eco apareceu depois que você abriu o salão vizinho, não corra para silenciar o arquivo inteiro. Primeiro veja qual porta você abriu.
Bônus do PostgreSQL: DISTINCT ON
No PostgreSQL existe uma construção especial:
SELECT DISTINCT ON (expressão) ...
Ela é diferente do DISTINCT comum.
O DISTINCT comum remove as linhas do resultado que são totalmente iguais.
Por exemplo:
SELECT DISTINCT category, status
FROM products;
mantém as combinações únicas de category + status.
Já o DISTINCT ON diz:
Deixe apenas uma linha para cada valor desta expressão.
Por exemplo, você precisa escolher um produto de cada categoria:
SELECT DISTINCT ON (category)
category, name, price
FROM products
ORDER BY category, price DESC;
Essa consulta pode ser lida assim:
- separe as linhas por
category; - dentro de cada categoria, ordene os produtos do mais caro para o mais barato;
- fique com a primeira linha de cada categoria.
O resultado pode ser assim:
| category | name | price |
|---|---|---|
| Acessórios | Casinha-portal | 3000 |
| Brinquedos | Brinquedo “Rato Laser” | 1700 |
| Livros | Livro “SQL para catonautas” | 2500 |
| Ração | Ração “Atum Lunar” | 1900 |
Aqui o DISTINCT ON (category) deixa uma linha por categoria, e o ORDER BY category, price DESC define exatamente qual linha vem primeiro dentro de cada categoria.
Importante:
DISTINCT ON é uma extensão do PostgreSQL. No SQL padrão e em muitos outros SGBDs ele não existe.
No nível básico, o importante não é decorá-lo como ferramenta obrigatória, e sim entender a diferença:
- o
DISTINCTmantém as linhas únicas do resultado; - o
DISTINCT ONdo PostgreSQL mantém a primeira linha de cada grupo definido pela expressão indicada.
Interview question
Pergunta de entrevista:
O que o DISTINCT faz no SELECT?
Resposta forte:
O DISTINCT remove as linhas repetidas do resultado da consulta. Ele não altera os dados da tabela nem apaga duplicatas do banco em si. Ele age só no nível da saída. Se várias colunas forem selecionadas, a unicidade é avaliada pela combinação inteira das colunas escolhidas.
Pergunta de entrevista:
O que SELECT DISTINCT category, status FROM products retorna?
Resposta forte:
Ele retorna os pares únicos de category + status. Não é a lista de categorias únicas nem a lista de status únicos, tomadas separadamente. Se uma mesma categoria aparece com status diferentes, ela vai surgir várias vezes no resultado — uma vez para cada par único.
Pergunta de entrevista:
Em que SELECT DISTINCT x FROM t difere de SELECT x FROM t GROUP BY x?
Resposta forte:
Sem funções de agregação, o resultado costuma ser o mesmo: as duas consultas retornam os valores únicos de x. Mas a finalidade é diferente. O DISTINCT é usado quando você quer um dicionário de valores: quais valores existem, afinal. O GROUP BY é usado quando você precisa dividir as linhas em grupos e calcular algo em cada grupo, por exemplo COUNT(*), SUM(...), AVG(...).
Pergunta de entrevista:
O DISTINCT garante a ordenação do resultado?
Resposta forte:
Não. O DISTINCT remove as repetições, mas não define a ordem das linhas. Se você precisa de uma ordem previsível, use o ORDER BY.
Pergunta de entrevista:
Quando um DISTINCT na consulta pode ser sinal de erro?
Resposta forte:
O sinal de alerta é um DISTINCT acrescentado só para tirar as duplicatas que apareceram depois de um JOIN. Muitas vezes essas duplicatas vêm da multiplicação de linhas numa junção um-para-muitos ou de uma condição de junção errada. Nessa situação, o DISTINCT mascara o sintoma e ainda dá ao banco o trabalho extra de deduplicar. O certo é entender por que as linhas se multiplicaram e corrigir a lógica da consulta.
DISTINCT faz em um SELECT?SELECT DISTINCT category, status FROM products?DISTINCT e ORDER BY?QUERY: O
DISTINCTserve para quando você silencia o eco de propósito. Se você não sabe de onde o eco veio, procure primeiro a fonte.