How to Join Two CSV Files Using SQL
You have two CSV files that share a column — orders and customers, transactions and accounts, employees and departments — and you need them side by side. This is a database join, and you can do it without a database.
The Usual Options, and Why They Grate
| Approach | The problem with it |
|---|---|
| Excel VLOOKUP / XLOOKUP | Works, but you are writing one formula per column you want to pull across, then copying it down thousands of rows. It breaks silently when the lookup column is not the leftmost, when types do not match, or when someone sorts the sheet. Fine for one column; miserable for eight. |
| Python and pandas | The right tool if you already have it. Installing Python, pip and pandas to combine two files once is a lot of ceremony for a ten-minute job. |
| Load into a real database | Correct and robust. Also means a server, a schema, CREATE TABLE statements, an import step, and usually somebody else's permission. |
| An online CSV merge tool | Quick — but you are uploading both files to a stranger's server, which is often not allowed and rarely checked. |
There is a fourth option: write the SQL you already know, and have it run in your browser against the files directly.
The Example
Two files. orders.csv:
order_id,customer_id,amount,status 1001,C001,250.00,paid 1002,C003,89.50,pending 1003,C001,410.00,paid 1004,C002,15.75,refunded 1005,C004,120.00,paid
And customers.csv:
customer_id,name,city C001,Acme Ltd,Austin C002,Borden Group,Denver C003,Castle Co,Austin C005,Delta Partners,Boston
They share customer_id. Note that order 1005 refers to C004, who is not in the customer list, and that customer C005 has no orders. Real data always looks like this, and which join you pick decides what happens to those two.
Step by Step
- Open the tool. Go to the main page. Nothing to install.
- Drop both files in at once. Drag them together onto the File Select panel.
- Check the aliases. Each file becomes a SQL table named by a single letter, shown in the Alias column. The letter comes from the start of the filename —
orders.csvbecomesO,customers.csvbecomesC. If two files would claim the same letter, the second is given another free one. Glance at that column before writing anything, and type a different letter if you would rather have one. - Write the join.
SELECT O.order_id, C.name, C.city, O.amount, O.status FROM O JOIN C ON O.customer_id = C.customer_id
- Run it with the Run Query button or
Ctrl+Enter.
Four rows come back — every order whose customer exists in the second file:
| order_id | name | city | amount | status |
|---|---|---|---|---|
| 1001 | Acme Ltd | Austin | 250 | paid |
| 1002 | Castle Co | Austin | 89.5 | pending |
| 1003 | Acme Ltd | Austin | 410 | paid |
| 1004 | Borden Group | Denver | 15.75 | refunded |
Order 1005 is gone, because customer C004 does not exist in customers.csv. That is a plain JOIN — also written INNER JOIN — doing exactly what it is supposed to: keep only rows that match on both sides.
250.00 came back as 250. Columns that look numeric are treated as numbers, so trailing zeros in the source text are not preserved — which is what lets you write WHERE amount > 100 without quoting. Values that would be damaged by that, like a reference such as 00042, are deliberately left as text instead.Try it with your own two files.
Open the tool →Keeping the Unmatched Rows
Dropping order 1005 is usually not what you want — an order with a missing customer record is precisely the thing worth finding. Use a LEFT JOIN to keep every row from the left-hand file regardless:
SELECT O.order_id, C.name, O.amount FROM O LEFT JOIN C ON O.customer_id = C.customer_id
Now all five orders come back, and order 1005 has an empty name. To list only the broken ones:
SELECT O.order_id, O.customer_id, O.amount FROM O LEFT JOIN C ON O.customer_id = C.customer_id WHERE C.customer_id IS NULL
That is a data-quality report, written in three lines, that VLOOKUP makes genuinely awkward.
Which Join to Use
| Join | Keeps | Use it when |
|---|---|---|
JOIN (inner) | Only rows matching on both sides | You want complete records and nothing else |
LEFT JOIN | Everything from the table named before LEFT JOIN; blanks where the other has no match | That file is your source of truth and you are enriching it |
LEFT JOIN, count the blanks, then decide. If you begin with an inner join you will never see what you quietly threw away.Going Further Than a Lookup
Once the files are joined, everything else in SQL is available. Total paid revenue per city, for example:
SELECT C.city, COUNT(*) AS order_count, SUM(O.amount) AS revenue FROM O JOIN C ON O.customer_id = C.customer_id WHERE O.status = 'paid' GROUP BY C.city ORDER BY revenue DESC
Doing that with lookup formulas means a helper column, a pivot table, and a filter that somebody will forget to update. Here it is one query.
When a Join Returns Nothing
An empty result is the classic frustration. It is almost always one of these four, and all are quick to check.
1. The values do not actually match
Trailing spaces and differing case are the usual culprits — "C001 " and "C001" are different strings. Look first:
SELECT customer_id FROM O LIMIT 10
Then force both sides into the same shape:
SELECT O.order_id, C.name FROM O JOIN C ON UPPER(TRIM(O.customer_id)) = UPPER(TRIM(C.customer_id))
2. One side is a number and the other is text
Numeric-looking values are detected as numbers automatically, so WHERE id = 42 works without quotes. But if one file has 00042 — which is kept as text, because stripping the leading zeros would corrupt it — and the other has 42, they will not match. Line them up explicitly:
SELECT O.order_id, C.name FROM O JOIN C ON CAST(O.customer_id AS STRING) = CAST(C.customer_id AS STRING)
3. You guessed the alias letters
This catches people out more than anything else on this list. The alias is not simply A for the first file you added — it is derived from the filename, so orders.csv is O. Write a query against A and B out of habit and you get an error about an unknown table, or worse, you join the wrong pair. The Alias column in the File Select panel is the authority, and the letter is editable.
4. The column name is ambiguous
When both files have a column called customer_id, always qualify it — O.customer_id, not bare customer_id. Unqualified, it is genuinely unclear which file you mean.
A Note on Duplicates
If the right-hand file has more than one row per key, a join multiplies rows: two matching customer rows for one order produces two output rows, and your totals silently double. Check before you trust the numbers:
SELECT customer_id, COUNT(*) AS n FROM C GROUP BY customer_id HAVING COUNT(*) > 1
If that returns anything, deduplicate or tighten the join condition before reporting any sums.
Exporting the Result
Click Export in the Query Results panel and choose CSV, JSON, Excel or Parquet. The file is generated in your browser and saved straight to your downloads folder.
Where the Files Go
Nowhere. Both CSVs are read and joined inside your browser tab; neither is transmitted to any server. You can confirm this by loading the page, disconnecting from the network entirely, and then doing the whole join offline — it works. See querying without uploading for the full explanation.
Two files, one query, nothing uploaded.
Open the tool →