PoppyOS

Home › Guides

How to find rows in one CSV file that are missing from another

Typical jobs: which customers are not yet on the newsletter list, which orders in the shop export are missing from the accounting export, which people from the old list did not make it into the new one. It is a "set difference": everything in file A whose key does not appear in file B.

Normalise the key before you compare

The most common reason this goes wrong is not the logic, it is the data: Bob@Example.com (capital letters, trailing space) is the same person as bob@example.com, but a computer sees two different strings. Trim spaces and fold case on the key before comparing, and decide separately whether you also want to match on other columns.

Python

Customers who are not on the newsletter list, matched by email address (customers.csv has the customers, newsletter.csv the subscribers):

import csv

def read(path):
    with open(path, newline="", encoding="utf-8-sig") as f:
        return list(csv.DictReader(f))

def norm(value):
    return value.strip().lower()

subscribed = {norm(r["Email"]) for r in read("newsletter.csv")}
customers = read("customers.csv")
missing = [r for r in customers if norm(r["Email"]) not in subscribed]

for r in missing:
    print(r["Email"], r["Plan"])
print(len(missing), "of", len(customers), "customers are not subscribed")
cy@example.com Pro
dee@example.com Basic
2 of 4 customers are not subscribed

Note that bob@example.com is correctly treated as subscribed even though the newsletter file has it as Bob@Example.com . Swap the two files to get the opposite answer (subscribers who are not customers). If you need both directions at once, that is a full comparison: see comparing two CSV files.

PowerShell

$have = (Import-Csv newsletter.csv).Email | ForEach-Object { $_.Trim().ToLower() }
Import-Csv customers.csv |
  Where-Object { $have -notcontains $_.Email.Trim().ToLower() } |
  ConvertTo-Csv -NoTypeInformation
"Email","Plan"
"cy@example.com","Pro"
"dee@example.com","Basic"

Line-based tools and why they are risky here

Short shell recipes using comm, grep -vf or awk are popular, and they work when every line is a simple, sorted, unquoted list. They split on commas without understanding quotes, so a name like "Moss, Cy" shifts every column after it, and they cannot see a line break inside a cell. Use them for quick checks on clean data, and a CSV parser when the file came from the real world.

Spreadsheet version

In Excel or Google Sheets, put the keys of file B on a second sheet and add a helper column next to file A with =COUNTIF(Sheet2!A:A, A2)=0. Rows showing TRUE are missing from B. We have not run this formula in Excel ourselves; it relies only on the standard COUNTIF behaviour, which ignores upper and lower case but not stray spaces, so clean the keys with TRIM first.

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.

More guides

All guides