SQL Playground: Query Files in Your Browser

After reading this you will know how to load CSV, Excel and Parquet files as SQL tables, write joins and aggregations that run entirely in your browser, and read the results without being tricked by type inference or memory limits.

What it is, with one query

The SQL Playground turns files into tables and runs real SQL against them. Drop a file named sales.csv and you get a table called sales. From there you write standard SQL. The engine is DuckDB compiled to WebAssembly, so everything happens inside the browser tab. No data is uploaded anywhere.

Here is the hook. Suppose sales.csv has 1,000 rows with columns region, product and amount. This query answers "how much did each region sell?" in one pass:

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

That is not a screenshot of a database server. It runs on the file you just dropped, using about 35 MB of engine code that downloads once and then stays cached.

When to use it, and when not

Use the Playground when you have tabular files and a question that SQL expresses cleanly: filtering, grouping, joining two files on a shared key, ranking within groups, or computing running totals. SQL is compact for these. A three-table join with a filter and a sort is four lines here and a fiddly afternoon in a spreadsheet.

Do not reach for it when a simpler tool already fits. If you only want to look at a CSV, sort it and delete a column, the CSV Viewer & Editor is faster. If you want a quick cross-tab without writing SQL, the Pivot Table Maker does grouping through menus. If you just need to see inside a Parquet file, the Parquet & Feather Preview shows the schema and a sample without a query.

Files load fully into RAM. A 200 MB CSV can expand to several times that in memory once parsed into typed columns. A desktop database would spill to disk; this cannot. If a query fails on a multi-gigabyte file, that is the memory ceiling, not a bug.

How files become tables

Each file is registered under a table name derived from its filename, with the extension stripped. orders_2024.csv becomes orders_2024. A leading digit or a space forces you to quote the name: write SELECT * FROM "2024 orders".

Column types are inferred, not declared. DuckDB reads a sample of rows and guesses. This is where surprises begin. A column of postal codes like 01234 may be read as the integer 1234, dropping the leading zero. A column mixing 12 and N/A becomes text, so SUM on it will error or return zero.

You can override inference by calling the reader function directly instead of relying on the auto-registered table:

SELECT * FROM read_csv_auto('sales.csv', types={'zip': 'VARCHAR'})

Here read_csv_auto is the CSV reader, 'sales.csv' is the file, and types pins the zip column to text so leading zeros survive.

Grouping and aggregation, the core idea

Most useful queries collapse many rows into few. GROUP BY region takes 1,000 sales rows and produces one row per distinct region. The aggregate functions decide what each group becomes:

\text{count} = \sum_{i \in g} 1, \quad \text{sum} = \sum_{i \in g} x_i, \quad \text{avg} = \frac{1}{n_g}\sum_{i \in g} x_i

Here g is one group, i ranges over the rows in that group, x_i is the value of the aggregated column, and n_g is the number of rows in the group. Count ignores the values and just tallies rows. Sum adds them. Average divides sum by count.

The mental model: SQL sorts rows into buckets by the GROUP BY key, then computes one number per bucket. A row with a NULL amount is skipped by SUM and AVG but still counted by COUNT(*). That mismatch explains many "the numbers do not add up" moments.

Reproducing the demo dataset

The sample dataset ships with the tool, so you can run this without uploading anything. Assume the demo sales table holds these four rows for two regions:

Sample rows from the demo sales table
regionproductamount
NorthWidget120
NorthGadget80
SouthWidget200
SouthGadget50
  1. Group by region. North gets rows 1 and 2; South gets rows 3 and 4.
  2. Sum amount per group. North is 120 + 80 = 200. South is 200 + 50 = 250.
  3. Order by that sum descending. South (250) comes first, North (200) second.
  4. Average per group for a sanity check. North is 200 / 2 = 100. South is 250 / 2 = 125.

The query SELECT region, SUM(amount) AS total, AVG(amount) AS mean FROM sales GROUP BY region ORDER BY total DESC returns two rows: South with 250 and 125, then North with 200 and 100.

Two bars matching the worked example: South at 250, North at 200.

Window functions, the part spreadsheets cannot do easily

A window function computes across a set of rows without collapsing them. You keep all four sales rows but attach a per-region rank or running total to each. The clause OVER (PARTITION BY region ORDER BY amount DESC) defines the window.

RANK() OVER (PARTITION BY region ORDER BY amount DESC)

PARTITION BY region restarts the ranking for each region. ORDER BY amount DESC ranks largest first. In North, Widget (120) gets rank 1 and Gadget (80) rank 2. In South, Widget (200) gets rank 1 and Gadget (50) rank 2. All four rows survive; each just gains a rank column.

To keep only the top row per region, wrap it in QUALIFY rank = 1. That is DuckDB shorthand that filters on a window result without a subquery.

A toggle compares two modes on the four demo rows. In GROUP BY mode the table shows two rows (South 250, North 200). In window mode the table shows all four rows, each with a running total that resets per region: North accumulates 120 then 200, South accumulates 200 then 250.

Joining across files

The Playground shines when two files share a key. Drop sales.csv and regions.csv where regions maps each region to a manager. Join them:

SELECT s.region, r.manager, SUM(s.amount) FROM sales s JOIN regions r ON s.region = r.region GROUP BY 1, 2

The join key is region. An inner JOIN keeps only rows where a match exists in both files. If regions is missing the South entry, South sales vanish silently from the result. Use LEFT JOIN to keep every sales row and get NULL for the missing manager, which makes the gap visible instead of hidden.

Common mistakes

Selecting a non-grouped column
Writing SELECT region, product, SUM(amount) ... GROUP BY region fails or returns an arbitrary product, because SQL cannot pick one product for a group of many. Either group by it too or aggregate it.
Trusting inferred types
A version column like 1.10 read as a number becomes 1.1, and IDs with leading zeros lose them. Pin the type with the reader function when the column is really text.
Counting rows instead of values
COUNT(*) counts all rows including NULLs; COUNT(amount) counts only non-NULL amounts. If 50 of 1,000 rows have a NULL amount, these differ by 50.
Filtering after grouping by mistake
WHERE filters rows before grouping; HAVING filters groups after. To keep regions whose total exceeds 200, use HAVING SUM(amount) > 200, not WHERE.

Related tools

Once you have an answer, other tools finish the job. Export the result and chart it in the Data Visualizer, or turn a CSV into JSON with the CSV ↔ JSON Converter. If your source is an Excel workbook, the Excel → CSV Converter gives you one clean CSV per sheet to drop in here. To view JSON as a table before querying, use the JSON Table Viewer. To mask sensitive columns before sharing a result, use the Data Anonymizer. To format a small result for a document, paste it into the Markdown Table Generator.

Frequently asked questions

Does my data get uploaded to a server?

No. The engine runs in your browser as WebAssembly. Files are read from your device into the tab's memory and never sent anywhere. Only the 35 MB engine downloads once from a CDN, then caches.

What SQL dialect does it accept?

DuckDB's dialect: standard SQL plus analytics extras like window functions, QUALIFY, PIVOT, list and struct types, and reader functions such as read_csv_auto(). Most PostgreSQL-style queries run unchanged.

Why did my big file fail to load?

Everything lives in RAM. A file that expands beyond the memory the browser tab can allocate will fail. There is no disk spill. Reduce columns, filter rows first, or convert to Parquet, which loads more compactly than CSV.

How do I query a file whose name starts with a number?

Quote the table name with double quotes: SELECT * FROM "2024_sales". The table name is the filename minus its extension.

Can I join three or more files at once?

Yes. Drop all the files, then chain JOIN clauses on their shared keys. There is no fixed limit beyond the memory needed to hold every file at once.