diff old.csv new.csv responde à pergunta errada. Ele compara linhas, então um
arquivo ordenado de outro jeito volta como "tudo mudou", e uma linha em que um
único campo foi corrigido volta como uma exclusão mais uma adição, e não como uma
alteração.
O que você quer é uma comparação por chave: quais linhas são novas, quais sumiram e, nas linhas presentes nos dois arquivos, quais campos são diferentes. São três perguntas, e elas precisam de três respostas diferentes.
Comece pela estrutura
Antes de comparar o conteúdo, confira se os dois arquivos são comparáveis:
head -n 1 old.csv new.csv
wc -l old.csv new.csv
Um cabeçalho alterado é o motivo mais comum de comparações sem sentido. Se uma coluna foi adicionada, renomeada ou reordenada entre as duas exportações, toda comparação campo a campo perde o sentido até você levar isso em conta. O número de linhas é o segundo sinal: uma diferença de três é uma semana normal; uma diferença de 40.000 geralmente significa que uma exportação foi truncada ou filtrada.
A comparação de três perguntas em SQL
Escolha primeiro a sua chave: a coluna que identifica o mesmo registro nos dois
arquivos. id, order_id, email. Se não houver nenhuma, vá para a última seção.
Linhas só no arquivo novo:
duckdb -c "SELECT * FROM read_csv('new.csv') n WHERE n.id NOT IN (SELECT id FROM read_csv('old.csv'))"
Linhas só no arquivo antigo:
duckdb -c "SELECT * FROM read_csv('old.csv') o WHERE o.id NOT IN (SELECT id FROM read_csv('new.csv'))"
Linhas nos dois, com um campo alterado:
duckdb -c "SELECT o.id, o.status old_status, n.status new_status FROM read_csv('old.csv') o JOIN read_csv('new.csv') n USING (id) WHERE o.status IS DISTINCT FROM n.status"
Usar IS DISTINCT FROM em vez de <> é o detalhe que importa: <> retorna NULL
quando qualquer um dos lados é NULL, então um campo que passou de vazio para
preenchido, ou de preenchido para vazio, some dos seus resultados sem avisar. Essa
é a mudança que você provavelmente mais queria encontrar.
Para pegar mudanças em todas as colunas de uma vez, sem citá-las:
duckdb -c "SELECT * FROM read_csv('old.csv') EXCEPT SELECT * FROM read_csv('new.csv')"
EXCEPT compara linhas inteiras e ignora a ordem, o que o torna um diff muito
melhor que o diff. Ele diz que uma linha mudou, não qual campo; por isso, combine-o
com o join acima quando já souber quais linhas olhar.
Quando os arquivos só estão ordenados de outro jeito
Se você só precisa saber se o conteúdo é o mesmo, ordene e calcule o hash:
tail -n +2 old.csv | sort | md5sum
tail -n +2 new.csv | sort | md5sum
Mesmo hash, mesmo conteúdo, não importa a ordem das linhas. Um hash diferente não diz nada sobre o que mudou, mas leva um segundo e resolve a pergunta "essas duas exportações são a mesma?", que muitas vezes é a investigação inteira.
Para um diff por linhas que sobreviva à reordenação:
diff <(sort old.csv) <(sort new.csv)
Continua sendo baseado em linhas, então uma mudança de um campo ainda aparece como um par, mas pelo menos a ordenação deixa de dominar a saída.
Ferramentas dedicadas
csvdiff e daff comparam por chave e geram uma saída que diz "a coluna
status mudou de A para B na linha 41", em vez de mostrar duas linhas. Se você faz
isso com frequência, instale uma das duas.
daff diff old.csv new.csv --output diff.html gera uma tabela colorida que dá para
ler de relance, o que nenhuma das opções acima consegue.
Git, se os arquivos estiverem versionados. Definir um driver de diff para CSV
em .gitattributes faz o git diff em um arquivo de dados passar de inútil a
útil, e é uma mudança de duas linhas.
Excel, sendo honesto, é ruim nisso. A abordagem de sempre, um PROCV
(VLOOKUP) por coluna para a outra planilha, funciona com arquivos pequenos, é
quadrática nos grandes e produz uma grade de #N/D difícil de ler. A mesclagem do
Power Query é muito melhor e quase ninguém a usa.
Lendo as diferenças no celular
A versão mais comum no dia a dia: duas exportações com uma semana de intervalo, no seu e-mail, e a pergunta é "o que mudou?", não "gere um relatório de conciliação".
O caminho prático não é um diff. Adicione uma coluna marcando a origem, empilhe os dois arquivos, ordene pela chave e leia os pares. Os campos alterados ficam lado a lado porque as duas versões de cada registro agora estão adjacentes. Para algumas dezenas de linhas, isso é mais rápido que qualquer ferramenta.
SQL direto no arquivo é a comparação inteira: um join pela chave com IS DISTINCT FROM encontra os campos alterados, e o assistente de IA cuida da versão da pergunta que você prefere fazer em palavras em vez de escrever.
O que ele fazNúmeros, datas e as diferenças falsas
Uma comparação costuma apontar um grande número de "mudanças" que não são. Descarte essas antes de investigar:
- Espaços no final.
"active"e"active "são diferentes. Compare valores comtrim(), a menos que os espaços tenham significado, o que quase nunca acontece. - Formatação de números.
1000,1,000e1000.00são um número e três strings. Converta os dois lados para um tipo numérico antes de comparar. - Formatos de data.
01/02/2026versus2026-02-01e, pior,01/02/2026significando fevereiro em um arquivo e janeiro em outro. Converta os dois para datas em vez de comparar texto. - Ruído de ponto flutuante.
0.1 + 0.2não é0.3em nenhuma linguagem que você use. Arredonde para a precisão que importa antes de comparar decimais. - Codificação. Um nome que aparece como
Joséem um arquivo eJoséno outro é uma diferença de codificação, não uma mudança nos dados.
Cada um desses pontos produz milhares de diferenças em um arquivo grande, e é assim que uma comparação acaba "mostrando tudo como alterado".
Quando a comparação vira correção, como normalizar uma coluna, arredondar um decimal ou corrigir as linhas que realmente mudaram, uma coluna de fórmula aplica isso a todas as linhas, e o resultado é salvo dentro do CSV.
O que ele fazQuando não há chave
Às vezes os arquivos não têm identificador: duas exportações de um log, duas listas de transações. Você tem duas opções, e ambas são concessões.
Montar uma chave composta com as colunas que juntas identificam uma linha: data mais valor mais descrição, por exemplo. Funciona quando a combinação é realmente única e falha em silêncio quando não é: duas transações idênticas no mesmo dia viram uma, e a comparação relata uma exclusão fantasma.
Comparar como multiconjuntos. Conte as ocorrências de cada linha distinta em cada arquivo e compare as contagens. É o que você precisa para logs e transações, em que a mesma linha aparece legitimamente mais de uma vez e a mudança relevante é quantas vezes:
duckdb -c "SELECT *, count(*) n FROM read_csv('old.csv') GROUP BY ALL"
Execute nos dois arquivos e compare as colunas n. É a única abordagem que acerta
em dados com muitas duplicatas.