How to compare two CSV files and see exactly what changed
You have last month's export and this month's export, and you need to know what is different: which rows are new, which are gone, and which values changed. Excel can do it with some effort, a script does it in a few lines, and a purpose-built tool does it with a click. This guide shows the script route in full (it is short), the PowerShell route for Windows, and the spreadsheet route in outline, with what each can and cannot see.
First decide how rows are matched
"Different" only means something once you say which row in the old file corresponds to which row in the new file. Almost every real comparison needs a key column, such as an order number or customer ID. Comparing line by line (what a plain text diff does) reports rows as changed as soon as the file is re-sorted or one row is inserted at the top. With a key, a re-sorted file with one changed price produces exactly one difference.
Python, no extra packages
This uses only Python's standard library. It reads both files, matches rows by the id column, and reports added rows,
removed rows and changed cells. It also warns if an ID appears twice in one file, because a repeated key would otherwise
silently overwrite the earlier row.
import csv
def load(path, key):
rows = {}
with open(path, newline="", encoding="utf-8-sig") as f:
for row in csv.DictReader(f):
if row[key] in rows:
print(f"warning: {key} {row[key]} appears more than once in {path}")
rows[row[key]] = row
return rows
old, new = load("old.csv", "id"), load("new.csv", "id")
print("added: ", [k for k in new if k not in old])
print("removed:", [k for k in old if k not in new])
for k in old:
if k in new:
for col, before in old[k].items():
after = new[k].get(col)
if after != before:
print(f"id {k}: {col} changed from {before!r} to {after!r}")
added: ['5'] removed: ['3'] id 2: price changed from '14.50' to '15.50'
Using utf-8-sig means a file saved by Excel with a byte-order mark is read correctly, instead of giving a first column
name with an invisible character in front of it (id would no longer equal "id"). Everything is compared as text, so 14.50 and 14.5 count as different. If you want them to be equal,
convert numeric columns with float() before comparing.
Windows PowerShell
Compare-Object compares whole rows. A row that exists on only one side shows <= (old file) or => (new file);
a changed row shows up as one of each.
$old = Import-Csv old.csv
$new = Import-Csv new.csv
Compare-Object $old $new -Property id,name,price |
Sort-Object id,SideIndicator |
ForEach-Object { "{0} {1} {2} {3}" -f $_.SideIndicator, $_.id, $_.name, $_.price }
<= 2 Gadget 14.50 => 2 Gadget 15.50 <= 3 Gizmo 4.00 => 5 Thingamajig 7.25
Reading the output: id 2 appears on both sides with different prices (a change), id 3 only on the old side (removed), id 5 only on the new side (added). PowerShell does not tell you which column changed; you work that out by eye, which gets tedious past a few columns.
In a spreadsheet
The usual approach is to put both files on separate sheets and use a lookup formula (VLOOKUP, or XLOOKUP in recent versions of Excel
and in Google Sheets) to pull the old value next to the new one, then a column that tests whether they are equal. This is fine for a few thousand rows and one or two columns.
It gets slow and error-prone with many columns, it needs a second pass to find removed rows, and spreadsheets can silently change values when they open a CSV
(see leading zeros). We have not timed spreadsheet formulas ourselves, so treat that as general experience, not a measurement.
What to watch for, whichever way you do it
- Repeated keys. If the same ID appears twice in a file, decide which copy counts. The script warns; do not ignore the warning.
- Invisible differences. A trailing space or different capitalisation makes
Ann@Example.comdiffer fromann@example.com. Decide whether that is a real change. - Columns that exist in only one file. A renamed column looks like one removed column and one added column. The script above only compares columns present in the old file.
- Line breaks inside cells. Line-based tools (
diff,comm,fc) split such a row in two. Use a real CSV parser, as above.
Last updated 2026-10-27. Code on this page was run on that date and the output shown is the real output. Spreadsheet menu steps are written from Microsoft's and Google's documented behaviour; where we have not run something ourselves, the page says so.