Para linhas duplicadas exatas, mantendo o cabeçalho e a ordem original:
awk 'NR==1 || !seen[$0]++' input.csv > deduped.csv
Isso resolve o caso fácil, e o caso fácil é mais raro do que parece. A maioria dos “duplicados” de um arquivo de trabalho não são linhas idênticas, e sim dois registros da mesma pessoa com maiúsculas e minúsculas diferentes, um espaço no final ou um telefone atualizado. Apagar o errado faz você perder dados, então a ordem das operações importa: decida o que torna duas linhas iguais, veja no que elas diferem e só então apague.
Passo um: defina a chave
Um duplicado são duas linhas que descrevem a mesma coisa. Quais colunas estabelecem isso é uma decisão sobre os seus dados, não uma propriedade do arquivo.
- Um duplicado de linha inteira, com todos os campos idênticos, quase sempre é um erro: uma importação dupla, um job que rodou de novo, uma inclusão que rodou duas vezes. Pode remover sem medo.
- Um duplicado de chave, mesmo
emailou mesmoorder_idmas todo o resto diferente, é uma questão de dados. Uma dessas linhas provavelmente é mais recente ou mais completa, e você precisa escolher.
Inverter os dois é a forma clássica de perder registros. Deduplicar por name numa lista de clientes vai apagar sem dó duas pessoas diferentes chamadas James Smith.
Passo dois: conte antes de apagar
Nunca rode uma deduplicação às cegas. Veja o que você está prestes a remover:
duckdb -c "SELECT email, count(*) n FROM read_csv('input.csv') GROUP BY email HAVING n > 1 ORDER BY n DESC LIMIT 20"
Dois números dizem se a sua chave está certa. Se os duplicados são um punhado, provavelmente são digitações duplas de verdade. Se um terço do arquivo está “duplicado”, a chave está errada: você está comparando algo que nunca foi único.
Depois olhe um caso específico por inteiro, que é o passo que todo mundo pula:
duckdb -c "SELECT * FROM read_csv('input.csv') WHERE email = '[email protected]'"
É aí que você descobre que as duas linhas diferem justamente na coluna que importava.
Passo três: escolha qual linha sobrevive
Quando você sabe que as linhas diferem, “remover duplicados” vira “ficar com a certa”. Três regras, mais ou menos na ordem de quantas vezes acertam:
Fique com a mais recente. Se houver um timestamp, normalmente é a resposta.
duckdb -c "COPY (SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY email ORDER BY updated_at DESC) rn FROM read_csv('input.csv')) WHERE rn = 1) TO 'deduped.csv' (HEADER)"
Fique com a mais completa. Sem timestamp, ordenar pelo número de campos não vazios é um bom substituto, porque o registro mais preenchido costuma ser o mais novo. Ordene por (col_a IS NOT NULL)::INT + (col_b IS NOT NULL)::INT + … e pegue a primeira.
Junte as duas. Às vezes cada linha tem algo que falta na outra. É comum e não existe atalho: é um coalesce por coluna, e para um conjunto pequeno vale mais fazer à mão do que automatizar mal para um grande.
Seja qual for a sua escolha, grave as linhas descartadas num segundo arquivo antes de apagá-las. Uma deduplicação que você não consegue desfazer é uma que você vai lamentar em algum momento das próximas duas semanas.
Quase duplicados, que são a maioria
A correspondência exata deixa passar a maior parte dos duplicados reais. Normalize primeiro, compare depois e mantenha os valores originais.
duckdb -c "SELECT lower(trim(email)) k, count(*) n FROM read_csv('input.csv') GROUP BY k HAVING n > 1"
As normalizações a aplicar, mais ou menos em ordem de rendimento:
- Maiúsculas e minúsculas.
[email protected]e[email protected]são a mesma caixa de e-mail. - Espaços. Espaços no final, vindos de um copiar e colar, são invisíveis e extremamente comuns.
trim()pega os do início e do fim; umreplacecuida dos espaços não separáveis que chegam de formulários web. - Pontuação em identificadores. Telefones como
+44 20 7946 0958,02079460958e(020) 7946 0958são um só número. Remova tudo que não for dígito antes de comparar. - Pontos do Gmail e endereços com plus.
[email protected]chega na mesma caixa de entrada que[email protected]. Se isso conta como duplicado depende de você estar deduplicando pessoas ou contas.
O que não fazer automaticamente: correspondência aproximada de nomes. “Jon Smith” e “John Smith” podem ser a mesma pessoa ou dois colegas, e um limite de similaridade que serve para um arquivo não serve para o próximo. Gere os pares candidatos, revise e só então aja.
O editor SQL e o editor de filtros visuais são as duas metades deste trabalho: um GROUP BY para achar as chaves duplicadas e depois um filtro para ler cada linha que compartilha uma, sem precisar carregar nada antes em um banco de dados.
O que ele fazNuma planilha
Excel e Sheets têm um comando Remover duplicatas (Remove Duplicates), com uma característica que convém conhecer antes de usá-lo: ele mantém a primeira ocorrência e apaga o resto, sem como dizer qual é a primeira. A ordem em que a planilha estava decide qual registro sobrevive.
Então ordene de propósito antes de executá-lo. Ordene pelo seu timestamp em ordem decrescente, e “manter a primeira” vira “manter a mais nova”, que geralmente é o que você queria.
A outra abordagem em planilha, uma coluna auxiliar com COUNTIF, é mais lenta, mas não destrutiva, e mostra os duplicados em vez de removê-los. Num arquivo grande, também é o que vai travar o Excel, porque COUNTIF em meio milhão de linhas é quadrático.
No celular
A versão realista disso no celular não é uma passada completa de deduplicação. É ordenar pela coluna que você suspeita, rolar a tela e ver as repetições lado a lado, que é o jeito mais rápido de descobrir se um arquivo tem mesmo um problema de duplicados.
Quando você consegue vê-los, filtrar por uma chave e ler as poucas linhas que a compartilham responde à única pergunta que importa: é o mesmo registro ou são dois registros que por acaso compartilham um campo?
Ordene por qualquer coluna como no Excel e depois apague linhas com o filtro aplicado, com segurança: linhas ocultas são protegidas, então uma exclusão nunca toca dados fora do filtro. Desfazer cobre tudo, e é isso que torna razoável fazer esse trabalho no celular.
O que ele fazVerificando
Três números, e eles devem bater:
wc -l input.csv deduped.csv
duckdb -c "SELECT count(*) rows, count(DISTINCT email) keys FROM read_csv('deduped.csv')"
rows e keys agora devem ser iguais. Se não forem, a deduplicação não foi aplicada a todos os grupos. E input − deduped deve ser igual ao número de linhas que você pretendia remover, o que você contou no passo dois. Se removeu mais que isso, a chave era mais larga ou mais estreita do que você pensava, e o arquivo de linhas descartadas que você guardou é agora o que salva a sua tarde.