Remover duplicatas em SQL significa uma de duas coisas: ocultar linhas repetidas do resultado de uma consulta com DISTINCT, ou excluir as cópias extras da tabela para que reste apenas uma de cada. Os exemplos funcionam em PostgreSQL, MySQL, SQLite e SQL Server.
Como remover duplicatas em SQL
SELECT DISTINCT retorna cada combinação das colunas listadas uma única vez e não altera a tabela. Para limpar a própria tabela, exclua todas as linhas exceto uma em cada grupo de duplicatas, geralmente a de menor id:
SELECT DISTINCT column_list FROM table_name;
DELETE FROM table_name
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM table_name GROUP BY column_list
) AS keepers
);
Aqui, Maria Lopez foi cadastrada três vezes:
-- Input: customers
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 3 | Maria Lopez | maria@example.com | 555-0142
-- 4 | Lena Novak | lena@example.com | 555-0121
-- 5 | Maria Lopez | maria@example.com | 555-0142
SELECT DISTINCT name, email, phone
FROM customers
ORDER BY name;
-- Output:
-- name | email | phone
-- ------------+-------------------+---------
-- Lena Novak | lena@example.com | 555-0121
-- Maria Lopez | maria@example.com | 555-0142
-- Sam Patel | sam@example.com | 555-0178
DELETE FROM customers
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM customers GROUP BY name, email, phone
) AS keepers
);
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Regras importantes
-
Liste as colunas que tornam duas linhas duplicadas. Deixe de fora o id e qualquer outra coisa que seja sempre diferente, como um timestamp
created_at. Linhas que diferem em qualquer coluna listada são todas mantidas. -
O wrapper
SELECT id FROM (...) AS keepersé para o MySQL, que de outra forma não permite que umDELETEleia a própria tabela em uma subconsulta: “You can’t specify target table ‘customers’ for update in FROM clause”. -
Confira antes de excluir. Execute primeiro a consulta com
GROUP BYouROW_NUMBER()para ver o que será removido, e faça a exclusão dentro de uma transação que você possa desfazer com rollback. - Evite novas duplicatas com um índice único depois que a tabela estiver limpa:
CREATE UNIQUE INDEX customers_email_key ON customers (email);
INSERT INTO customers VALUES (6, 'Maria Lopez', 'maria@example.com', '555-0142');
-- ERROR: duplicate key value violates unique constraint "customers_email_key"
Sintaxe alternativa
Em código SQL Server, é comum ver as cópias numeradas em uma CTE e excluídas por meio dela, algo que só o SQL Server permite. Na tabela customers original:
-- SQL Server
WITH numbered AS (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY name, email, phone ORDER BY id
) AS row_num
FROM customers
)
DELETE FROM numbered WHERE row_num > 1;
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Para combinar duas consultas sem repetir linhas, use UNION, que remove duplicatas, em vez de UNION ALL, que as mantém:
SELECT 'apple' AS fruit UNION ALL SELECT 'apple';
-- Output:
-- fruit
-- -----
-- apple
-- apple
SELECT 'apple' AS fruit UNION SELECT 'apple';
-- Output: apple
Quando você deve remover duplicatas?
Às vezes você só precisa ocultar repetições em um resultado; outras vezes, a própria tabela precisa de limpeza. Estes são os passos habituais, da verificação à exclusão.
Mostrar cada valor uma vez
Para um relatório, um dropdown ou uma verificação rápida do que há em uma coluna, SELECT DISTINCT remove as repetições do resultado e deixa a tabela como está.
Conte as cópias antes de excluir
Agrupe pelas colunas que definem uma duplicata e mantenha os grupos com HAVING COUNT(*) > 1. Isso mostra o que está duplicado e quantas vezes, antes de qualquer remoção. O guia para encontrar duplicatas em SQL traz mais formas de verificar.
Escolha qual cópia manter
ROW_NUMBER() numera as linhas em cada grupo de duplicatas. O ORDER BY dentro de OVER (...) decide qual linha recebe o número 1 e permanece: a mais antiga, a mais recente ou a mais completa.
Exclua as cópias extras
Depois de saber qual linha manter, exclua as outras. Para manter a linha mais recente em vez da mais antiga, numere as linhas por data, da mais recente para a mais antiga, e exclua tudo com número acima de 1.
Exemplos reais de remoção de duplicatas
Todos os países e transportadoras com que você trabalha
DISTINCT se aplica à linha inteira das colunas selecionadas, então Canada aparece uma vez para cada transportadora:
-- Input: shipments
-- order_id | country | carrier
-- ---------+---------+--------
-- 1001 | Canada | UPS
-- 1002 | Germany | DHL
-- 1003 | Canada | FedEx
-- 1004 | Canada | UPS
-- 1005 | Germany | DHL
SELECT DISTINCT country, carrier
FROM shipments
ORDER BY country, carrier;
-- Output:
-- country | carrier
-- --------+--------
-- Canada | FedEx
-- Canada | UPS
-- Germany | DHL
Endereços de e-mail que diferem apenas em maiúsculas e minúsculas
Agrupe pelo e-mail em minúsculas para que Jon@Example.com e jon@example.com contem como cópias. (MySQL e SQL Server já ignoram maiúsculas e minúsculas por padrão, e LOWER() não causa problemas neles.)
-- Input: accounts
-- id | email
-- ---+----------------------
-- 1 | Ana.Silva@example.com
-- 2 | jon@example.com
-- 3 | ana.silva@example.com
-- 4 | Jon@Example.com
-- 5 | mei@example.com
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM accounts
GROUP BY LOWER(email)
HAVING COUNT(*) > 1
ORDER BY email;
-- Output:
-- email | copies
-- ----------------------+-------
-- ana.silva@example.com | 2
-- jon@example.com | 2
Mantenha a cópia que tem telefone
Ordene primeiro as linhas que têm telefone e depois por id, para que a linha mais completa de cada grupo seja a número 1:
-- Input: contacts
-- id | email | phone
-- ---+------------------+---------
-- 1 | ravi@example.com | NULL
-- 2 | ravi@example.com | 555-0110
-- 3 | ines@example.com | 555-0133
-- 4 | ines@example.com | NULL
SELECT
id,
email,
phone,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY CASE WHEN phone IS NULL THEN 1 ELSE 0 END, id
) AS row_num
FROM contacts
ORDER BY email, row_num;
-- Output:
-- id | email | phone | row_num
-- ---+------------------+----------+--------
-- 3 | ines@example.com | 555-0133 | 1
-- 4 | ines@example.com | NULL | 2
-- 2 | ravi@example.com | 555-0110 | 1
-- 1 | ravi@example.com | NULL | 2
Excluir as linhas com número 2 ou maior (ids 1 e 4) deixa um contato completo por e-mail.
Exclua cópias exatas de uma tabela sem id
Quando as cópias são idênticas em todas as colunas, não há id para manter o menor. Copie as linhas distintas para uma nova tabela e substitua a antiga por ela. Recrie os índices e constraints que a tabela antiga tinha, pois a cópia não os mantém:
-- Input: page_views
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
-- /pricing | 2026-10-01 09:00:00
-- In SQL Server, use SELECT DISTINCT * INTO page_views_clean FROM page_views,
-- then EXEC sp_rename 'page_views_clean', 'page_views'
CREATE TABLE page_views_clean AS SELECT DISTINCT * FROM page_views;
DROP TABLE page_views;
ALTER TABLE page_views_clean RENAME TO page_views;
-- Output (page_views afterwards):
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
Leitura complementar sobre remoção de duplicatas
Neste blog:
- Guia para encontrar duplicatas em SQL: mais formas de identificar linhas duplicadas antes de removê-las
- Detectar linhas duplicadas em uma tabela do SQL Server: a abordagem do SQL Server
- Guia de SQL COUNT DISTINCT: contar valores únicos em vez de remover repetições
- Sintaxe e exemplos do comando SQL DELETE: o básico sobre exclusão de linhas
- Criando constraints únicas no PostgreSQL: impedindo duplicatas de vez
- Guia de CTE vs subconsulta em SQL: as duas formas de escrever a etapa “quais linhas manter”
Documentação oficial:
- PostgreSQL: DELETE e funções de janela
- MySQL: DELETE e ROW_NUMBER()
- SQLite: DELETE e funções de janela
- SQL Server: DELETE e ROW_NUMBER
Beekeeper Studio é um cliente de banco de dados gratuito e open source
A melhor ferramenta de consultas e edição de SQL que já usei. Tem tudo o que preciso para gerenciar meu banco de dados. - ⭐⭐⭐⭐⭐ Mit
O Beekeeper Studio é rápido, intuitivo e fácil de usar. Ele suporta vários bancos de dados e funciona muito bem no Windows, Mac e Linux.
O que os usuários dizem sobre o Beekeeper Studio
"O Beekeeper Studio substituiu completamente meu antigo fluxo de trabalho com SQL. É rápido, intuitivo e torna o trabalho com bancos de dados agradável de novo."
"Já experimentei muitas GUIs de banco de dados, mas o Beekeeper encontra o equilíbrio perfeito entre recursos e simplicidade. Simplesmente funciona."