3 min read

Query a CSV with SQL, right in your browser

Import a CSV, then answer questions with GROUP BY, a JOIN and a window function — against real SQLite compiled to WebAssembly, with nothing uploaded.

sqlsqlitedataapps

Some questions are awkward in a spreadsheet and trivial in SQL. "Top three products per region", "running total by month", "orders with no matching customer" — each is a formula-and-helper-column ordeal in a grid and one clear statement in SQL. SQL Playground is SQLite compiled to WebAssembly, so the SQL you write is the SQL SQLite runs, and your data stays in the browser's memory.

Getting a CSV in

Import a CSV and it becomes a table. Say you drop in a sales export that lands as a table named sales with columns region, product, month and amount. The schema sidebar lists it; click a table to select from it. Run a query with Ctrl or Cmd and Enter.

GROUP BY answers the aggregate questions

The first thing a spreadsheet makes hard is grouping by more than one key. In SQL it is the natural shape:

SELECT region, product, SUM(amount) AS total
FROM sales
GROUP BY region, product
ORDER BY region, total DESC;

That is every region-and-product combination with its total, sorted, in four lines. No pivot table, no dragging fields into boxes.

JOIN answers the questions that span two files

Grouping is easy in a spreadsheet compared to relating two of them. Import a second CSV as customers and you can ask which orders point at a customer that no longer exists:

SELECT s.region, s.product, s.amount
FROM sales AS s
LEFT JOIN customers AS c ON c.id = s.customer_id
WHERE c.id IS NULL;

A LEFT JOIN keeps every sale and leaves the customer columns null where there was no match; filtering on that null is how you find the orphans. Doing this with lookups across two sheets is the kind of task that quietly produces a wrong answer.

Window functions answer "compared to what"

The real gap is ranking and running totals. A window function computes across a set of rows without collapsing them, so each row keeps its identity and gains a number that depends on its neighbours:

SELECT region, month, amount,
       SUM(amount) OVER (
         PARTITION BY region ORDER BY month
       ) AS running_total,
       RANK() OVER (
         PARTITION BY region ORDER BY amount DESC
       ) AS rank_in_region
FROM sales;

Now every row carries its region's running total up to that month and its rank within the region. Reproducing that in a spreadsheet means fragile absolute-reference formulas that break the moment a row is inserted.

Why this beats a spreadsheet for some questions

A spreadsheet is a grid you look at; a query is a question you ask. When the question involves grouping by several keys, relating two datasets, or comparing a row to a window around it, SQL states it once and the engine does the bookkeeping. You also get an exact, repeatable answer — the same query on the same data always returns the same rows, which a hand-built pivot does not guarantee.

The whole database lives in the tab and is discarded when you close it. Results copy out as CSV. Because nothing is uploaded, a data extract you would never paste into a hosted query tool is safe to poke at here.

Try it

More writing