diff old.csv new.csv responde la pregunta equivocada. Compara líneas, así que un
archivo ordenado de otra forma sale como "todo cambió", y una fila en la que se
corrigió un solo campo sale como una eliminación más una adición, en lugar de
como un cambio.
Lo que necesitas es una comparación por clave: qué filas son nuevas, cuáles desaparecieron y, en las filas que están en ambos archivos, qué campos son distintos. Son tres preguntas, y necesitan tres respuestas distintas.
Empieza por la estructura
Antes de comparar el contenido, comprueba que los dos archivos sean comparables:
head -n 1 old.csv new.csv
wc -l old.csv new.csv
Un encabezado distinto es la causa más común de comparaciones sin sentido. Si se agregó, renombró o reordenó una columna entre las dos exportaciones, toda comparación campo por campo carece de sentido hasta que lo tengas en cuenta. El número de filas es la segunda señal: una diferencia de tres es una semana normal; una diferencia de 40.000 suele significar que una exportación se truncó o se filtró.
La comparación de tres preguntas en SQL
Elige primero tu clave: la columna que identifica el mismo registro en ambos
archivos. id, order_id, email. Si no hay ninguna, ve a la última sección.
Filas solo en el archivo nuevo:
duckdb -c "SELECT * FROM read_csv('new.csv') n WHERE n.id NOT IN (SELECT id FROM read_csv('old.csv'))"
Filas solo en el archivo anterior:
duckdb -c "SELECT * FROM read_csv('old.csv') o WHERE o.id NOT IN (SELECT id FROM read_csv('new.csv'))"
Filas en ambos, con un campo modificado:
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 en lugar de <> es el detalle que importa: <> devuelve
NULL cuando cualquiera de los dos lados es NULL, así que un campo que pasó de
vacío a lleno, o de lleno a vacío, desaparece de tus resultados sin avisar. Y ese suele ser
el cambio que más buscabas.
Para detectar cambios en todas las columnas a la vez sin nombrarlas:
duckdb -c "SELECT * FROM read_csv('old.csv') EXCEPT SELECT * FROM read_csv('new.csv')"
EXCEPT compara filas completas e ignora el orden, lo que lo convierte en un
diff mucho mejor que diff. Te dice que una fila cambió, no qué campo, así que
combínalo con el join anterior cuando ya sepas qué filas mirar.
Cuando los archivos solo están ordenados de otra forma
Si solo necesitas saber si el contenido es el mismo, ordena y calcula el hash:
tail -n +2 old.csv | sort | md5sum
tail -n +2 new.csv | sort | md5sum
Si el hash es igual, el contenido es el mismo, sin importar el orden de las filas. Si es distinto, no te dice qué cambió, pero tarda un segundo y resuelve la pregunta "¿es la misma exportación?", que muchas veces resulta ser todo el asunto.
Para un diff por líneas que sobreviva al reordenado:
diff <(sort old.csv) <(sort new.csv)
Sigue basándose en líneas, así que un cambio de un campo todavía aparece como un par, pero al menos el orden ya no domina la salida.
Herramientas dedicadas
csvdiff y daff comparan ambas por clave y producen una salida que dice
"la columna status cambió de A a B en la fila 41" en lugar de mostrarte dos
líneas. Si lo haces con regularidad, instala una de las dos.
daff diff old.csv new.csv --output diff.html genera una tabla con colores que se
lee de un vistazo, algo que ninguna de las opciones anteriores logra.
Git, si los archivos están versionados. Configurar un controlador de diff para
CSV en .gitattributes hace que git diff sobre un archivo de datos pase de
inútil a útil, y es un cambio de dos líneas.
Excel, siendo honestos, es malo para esto. El enfoque habitual, un BUSCARV
(VLOOKUP) por columna hacia la otra hoja, funciona con archivos pequeños, es
cuadrático en los grandes y produce una cuadrícula de #N/A difícil de leer. La
opción Combinar consultas de Power Query es mucho mejor y casi nadie la usa.
Leer las diferencias en el celular
La versión más común en la vida real: dos exportaciones con una semana de diferencia, en tu correo, y la pregunta es "¿qué cambió?" y no "genera un informe de conciliación".
La vía práctica no es un diff. Agrega una columna que marque el origen, apila los dos archivos, ordena por la clave y lee los pares. Los campos modificados se ven uno al lado del otro porque las dos versiones de cada registro quedan juntas. Para unas pocas docenas de filas, esto es más rápido que cualquier herramienta.
SQL directamente sobre el archivo es toda la comparación: un join por la clave con IS DISTINCT FROM encuentra los campos modificados, y el asistente de IA se encarga de la versión de la pregunta que prefieres hacer con palabras en vez de escribirla.
Qué haceNúmeros, fechas y las diferencias falsas
Una comparación suele arrojar una gran cantidad de "cambios" que no lo son. Descártalos antes de investigar:
- Espacios al final.
"active"y"active "son distintos. Compara valores contrim(), salvo que los espacios tengan significado, lo cual casi nunca ocurre. - Formato de números.
1000,1,000y1000.00son un número y tres cadenas de texto. Convierte ambos lados a un tipo numérico antes de comparar. - Formatos de fecha.
01/02/2026frente a2026-02-01y, peor aún,01/02/2026significando febrero en un archivo y enero en otro. Convierte ambos a fechas en lugar de comparar texto. - Ruido de punto flotante.
0.1 + 0.2no es0.3en ningún lenguaje que uses. Redondea a la precisión que importa antes de comparar decimales. - Codificación. Un nombre que se muestra como
Joséen un archivo y comoJoséen el otro es una diferencia de codificación, no un cambio en los datos.
Cada uno de estos puntos produce miles de diferencias en un archivo grande, y así es como una comparación termina "mostrando todo como cambiado".
Cuando la comparación se convierte en una corrección, como normalizar una columna, redondear un decimal o corregir las filas que sí cambiaron, una columna de fórmula la aplica a todas las filas y el resultado se guarda dentro del CSV.
Qué haceCuando no hay clave
A veces los archivos no tienen identificador: dos exportaciones de un log, dos listas de transacciones. Tienes dos opciones y ambas son un compromiso.
Construir una clave compuesta con las columnas que juntas identifican una fila: fecha más monto más descripción, por ejemplo. Funciona cuando la combinación realmente es única, y falla en silencio cuando no lo es: dos transacciones idénticas el mismo día se vuelven una, y la comparación reporta una eliminación fantasma.
Comparar como multiconjuntos. Cuenta las apariciones de cada fila distinta en cada archivo y compara los conteos. Es lo que necesitas para logs y transacciones, donde la misma fila aparece legítimamente más de una vez y el cambio relevante es cuántas veces:
duckdb -c "SELECT *, count(*) n FROM read_csv('old.csv') GROUP BY ALL"
Ejecútalo en ambos archivos y compara las columnas n. Es el único enfoque que
resuelve bien los datos con muchos duplicados.