Analistable

How to join two CSV files by a common column

By the Analistable team · Updated · 5 min read

To join two CSVs side by side on a shared column such as customer_id, load both into Excel with Data → From Text/CSV, then use Merge Queries. On the command line, csvjoin -c customer_id --left orders.csv customers.csv (csvkit) does it in one line, as does DuckDB: SELECT * FROM 'orders.csv' LEFT JOIN 'customers.csv' USING (customer_id).

Part of our guide: How to merge CSV files into one

The files

orders.csv and customers.csv
orders.csvcustomers.csv
order_idcustomer_idamountcustomer_idname
O-9001C-101120C-101Bakery Lune
O-9002C-10375C-102Hart & Co
O-9004C-10460C-103Nordic Supply

In Excel

  1. Data → Get Data → From File → From Text/CSV, choose orders.csv, then Load To → Only Create Connection. Repeat for customers.csv.
  2. Data → Get Data → Combine Queries → Merge. Select orders and customers, click customer_id in each.
  3. Choose Left Outer to keep every order, then expand the name column and Close & Load.

Set customer_id to Text in both queries before merging, so IDs with leading zeros match. More on join kinds: joining two tables in Excel.

On the command line

# csvkit (pip install csvkit)
csvjoin -c customer_id --left orders.csv customers.csv > joined.csv

# DuckDB
duckdb -c "COPY (SELECT * FROM 'orders.csv' LEFT JOIN 'customers.csv' USING (customer_id)) TO 'joined.csv' (HEADER)"

csvjoin does an inner join by default; --left, --right and --outer change it. DuckDB uses normal SQL join types and is much faster on large files.

In Python

import pandas as pd
orders = pd.read_csv("orders.csv", dtype={"customer_id": str})
customers = pd.read_csv("customers.csv", dtype={"customer_id": str})
orders.merge(customers, on="customer_id", how="left").to_csv("joined.csv", index=False)
joined.csv (left join)
order_idcustomer_idamountname
O-9001C-101120Bakery Lune
O-9002C-10375Nordic Supply
O-9004C-10460

Joining is different from stacking. To put rows from several CSVs with the same columns under each other, see merging CSV files.

Common problems

  • Leading zeros: an ID like 00123 becomes 123 when read as a number. Read key columns as text in every tool.
  • Different column names for the same key (customer_id vs CustomerID): rename one, or use left_on/right_on in pandas and ON a.customer_id = b.CustomerID in SQL.
  • Duplicate keys in the lookup file multiply rows in the result. Check with csvstat or a COUNTIF before joining.
  • Encodings and delimiters: semicolon-separated or non-UTF-8 files need the delimiter and encoding set explicitly.

Inner, left and full joins on the same files

Using the three orders and three customers above:

Rows returned by each join
JoinRowsWhat you get
Inner2O-9001 and O-9002, the orders with a known customer
Left (orders first)3All orders; O-9004 has an empty name
Full outer4All orders plus Hart & Co (C-102), who has no order in this file
Anti (orders with no customer)1O-9004
-- DuckDB: orders whose customer is missing from customers.csv
SELECT o.*
FROM 'orders.csv' o
ANTI JOIN 'customers.csv' c USING (customer_id);

-- Portable SQL version
SELECT o.*
FROM 'orders.csv' o
LEFT JOIN 'customers.csv' c ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;

Joining on two columns

If neither file has a unique single key (for example a price list per region and SKU), join on both columns: csvjoin -c "region,sku" with csvkit, on=["region", "sku"] in pandas, USING (region, sku) in DuckDB, and Ctrl+click both columns in the Power Query Merge dialog. Joining on SKU alone would attach every region's price to each row and multiply them.

Troubleshooting

Symptom, cause and fix
SymptomCauseFix
No rows matchOne file's key has a byte-order mark or quotes in the headerOpen in a text editor; rename the header, or set encoding to UTF-8 with BOM handling
Some IDs match, others don'tTrailing spaces or mixed caseTrim and upper-case both keys before joining
Result has more rows than orders.csvcustomers.csv repeats a customer_idDe-duplicate customers first
Numbers like 1.2E+10 in the outputLong IDs read as numbers by ExcelImport as text with Data → From Text/CSV and set the type
Dates shift or swap day and monthLocale differences between filesRead dates as text, then parse with the right format

Which tool when

  • Excel Power Query: when the result feeds an Excel report and you want to refresh it next month.
  • csvkit: quick one-off joins of small files on the command line.
  • DuckDB: large files, several conditions, or SQL you already know.
  • pandas: when the join is one step in a longer Python cleaning script.

Check every join the same way: count rows before and after, and count the rows with an empty value in a column from the second file. With the sample files, a left join must return 3 rows with 1 empty name; anything else means duplicates or a key mismatch.

In Google Sheets

Import both files into one spreadsheet with File → Import → Upload, choosing Insert new sheet(s) so each CSV gets its own tab. Then, in a new column on the orders tab:

=ARRAYFORMULA(IF(B2:B = "", , XLOOKUP(B2:B, customers!A2:A, customers!B2:B, "Not found")))

Column B of orders holds customer_id; the formula fills a name for every order and "Not found" for C-104. Google Sheets handles files up to its cell limit per spreadsheet, so very large CSVs are better joined with DuckDB or pandas.

Frequently asked questions

How do I merge two CSV files by a column?
Use Excel Power Query Merge Queries, csvjoin from csvkit, pandas merge or a DuckDB SQL join on the shared column.
Why don't my CSV rows match?
The key is probably read as a number in one file and as text in the other, or contains spaces. Read it as text and trim it.
Can I join CSV files without Excel?
Yes, with csvkit or DuckDB on the command line, or with pandas in Python.
Can I join two CSV files in Google Sheets?
Yes. Import both into one spreadsheet (File → Import), then use VLOOKUP or XLOOKUP inside ARRAYFORMULA to bring columns from one tab into the other.
How do I join CSV files on two columns?
Pass both columns as the key: csvjoin -c "region,sku", on=["region", "sku"] in pandas, USING (region, sku) in DuckDB, or Ctrl+click both columns in Power Query's Merge dialog.
How do I find rows in one CSV that aren't in the other?
Do a left join and keep rows where a column from the second file is empty, or use ANTI JOIN in DuckDB.

Related guides