Find field changes between two CSV exports
Match records by a unique identifier and separate added, removed, changed and unchanged records without relying on row order.
In this guide
Yesterday's order export and today's export contain the same number of rows. That does not mean nothing changed: one order may have disappeared, another arrived, and an existing order's amount changed. Comparing the two files line by line produces noise when rows move. This guide matches records by a stable identifier, then reports exactly which field values differ.
The existing two-list comparison guide checks whether identifiers occur in both lists. This guide goes further by comparing non-key fields for records present in both exports and retaining the old and new values for review.
Agree the key and comparison rule
Use an identifier that is unique within each file and retains its meaning across exports. This example uses record_id. A row number is unsuitable if sorting changes it. If one order legitimately has several line items, an order identifier alone is not a unique key. Ask for a stable line identifier or prepare a clearly defined composite key before using this helper. It supports one named key column.
The comparison is exact and case-sensitive. It does not trim values or convert amounts to numbers. Blank text differs from 0, and 10.00 differs from 10.0. This makes formatting changes visible alongside business changes. Decide which differences matter after reviewing the report. If numeric equivalence is your goal, define separators, precision, currency and missing-value rules before creating separate normalized inputs.
Both exports must contain the same set of header names, although their column order may differ. A renamed or missing column stops the comparison. Adding an empty column silently would otherwise make a schema change look like changes to every record.
Inspect the practice exports
Download the before export and the after export. Each has seven fictional records. The after file deliberately reorders both rows and columns. The key 123456789012345679 remains an exact string, and 001 retains its leading zeros.
| Identifier | Expected classification | Reason |
|---|---|---|
| 001 | unchanged | All non-key values match |
| 002 | changed | Status open becomes paid; amount 20.00 becomes 25.00 |
| 003 | changed | A missing amount becomes literal 0 |
| 004 | changed | A blank note becomes a note with a comma and embedded CRLF |
| 005 | removed | Present only in the before export |
| 006 | changed | Amount text 10.00 becomes 10.0 |
| 008 | added | Present only in the after export |
| 123456789012345679 | unchanged | Row and column movement do not change the values |
Expected totals are one added, one removed, four changed and two unchanged records, with five changed fields. The eight identifiers in this review are the union of two seven-record populations. Four changed records plus two unchanged records plus one removed record reconcile to the seven before records. Replace removed with added to reconcile to the seven after records.
Run the comparison
Download and extract the CSV delivery practice pack. Open a terminal in that folder and run:
python3 csv_delivery.py compare changes-before.csv changes-after.csv changes-output --key record_id
The helper requires Python 3.9 or newer and no additional packages. On Windows with the Python launcher installed, use py -3 in place of python3; that platform has not been tested for this release. The destination folder must not already exist, and its parent must exist. Use a new destination name on a repeated run. Input files are read without modification and nothing is uploaded.
The output folder contains record-status.csv, field-changes.csv and summary.json. The last file records a completed run, counts and source hashes. A PASS result means the comparison and report round-trip checks completed. It does not mean the exports are complete, the changes are authorized or the data is correct.
Diagnose a duplicate key before comparing amounts
In a text editor, make a copy of changes-before.csv named duplicate.csv. Copy the entire data line beginning with 002 to the end as a new line. In the extracted practice folder run:
python3 csv_delivery.py compare duplicate.csv changes-after.csv blocked --key record_id
Our November 12 run stopped with STOP: Duplicate key: 002. The two before rows would both claim the same after row. For this exercise, switch back to the untouched changes-before.csv. For a real export, inspect both records. If they represent separate line items, obtain a key unique to each line before comparing them.
Check the direction using one known order
Run the forward comparison above and open field-changes.csv. Then exchange the two inputs and use a fresh folder:
python3 csv_delivery.py compare changes-after.csv changes-before.csv reverse --key record_id
| What to inspect | Before → after | After → before |
|---|---|---|
| 002, status | open → paid | paid → open |
| 002, amount | 20.00 → 25.00 | 25.00 → 20.00 |
| 005, membership | removed | added |
| 008, membership | added | removed |
Both directions have one addition and one removal. Those totals alone would miss a reversed comparison. If the status of 002 points the wrong way, check the export timestamps and run again in a new folder before acting on the report. The forward reports matched both expected files field for field. Download the commands, errors and observed results.
Read the two reports together
record-status.csv lists each identifier once, its classification and its number of changed fields. Added and removed records show zero changed fields because there is no counterpart to compare. Their full values remain in the relevant source export. The report sorts identifiers as text, which may differ from numeric or original row order.
field-changes.csv contains one row per changed field for matched records. Its columns are key_value, field, before and after. Identifier 002 appears twice because two fields changed. The note for 004 remains one field even though it spans multiple physical lines in the CSV. Record 006 appears because this is an exact-string comparison, not a numeric tolerance test.
Compare your output with the downloadable expected statuses and expected field changes. When viewing these files in a spreadsheet, import identifiers as text. See leading-zero preservation and long identifiers. Opening a CSV by double-clicking can change how the application interprets the report.
Resolve exceptions before trusting the result
| Stop or surprising result | Action |
|---|---|
| Duplicate key | Resolve the record grain; do not arbitrarily keep the first duplicate |
| Blank or edge-spaced key | Correct or explicitly normalize a copy using an agreed rule |
| Header sets differ | Establish whether a column was renamed, added or removed |
| Many added and removed records | Check whether identifier formatting or case changed |
| Many changed values | Check export formatting, whitespace and newline conventions |
| No changes | Also verify export filters, source dates and population coverage |
An edited key appears as one removed record and one added record. The helper cannot infer that they describe the same real-world item. A missing record also does not prove deletion: a filter, access restriction or incomplete export could explain it. Keep the source export settings with your review notes.
The helper reads each input into memory, with a 5 MB file limit and a 1,000,000-character field limit. It expects UTF-8, an optional BOM, commas and double quotes. It rejects invalid structure and key ambiguity before creating reports. An interrupted write can leave an incomplete folder: do not use it without the completed summary.json. The output copies values as data without sanitizing spreadsheet formulas, so use fictional samples for practice and inspect untrusted exports appropriately.
Test record and continue
On October 29, 2026, local tests with Python 3.14.9 on macOS matched both reports to independently specified expected files. Checks covered reordered columns and rows, blank versus zero, amount formatting, long identifiers, embedded CRLF, identical inputs, reverse comparison, empty populations, duplicate and blank keys, case-sensitive identifiers and protection of an existing output folder. See the dated test record or run python3 test_csv_delivery.py from the extracted pack. No new Excel or Windows application test is claimed.
After reviewing the changes, reconcile group totals to check the numerical impact. If the checked export must be delivered in smaller batches, follow splitting by complete records. The helper relies on Python's documented CSV reader behavior; business interpretation remains a separate review step.