gg
Home › Dev & Data › SQL Playground
Dev & Data

SQL Playground

Paste a CSV and it becomes a table called t. Paste a second CSV and it becomes t2, so you can write a JOIN. Then write a real SELECT: column lists and AS aliases, WHERE with AND/OR/NOT, LIKE, IN, BETWEEN and IS NULL, GROUP BY with COUNT/SUM/AVG/MIN/MAX, HAVING, multi-key ORDER BY, DISTINCT, LIMIT and OFFSET, INNER JOIN and LEFT JOIN, and eight scalar functions. You get the result back as a table and as CSV. Nothing is uploaded — the query engine is compiled into the page and runs on your device, so this is safe to use on data you would never paste into an online SQL sandbox. It is a genuinely faster way to learn SQL than a tutorial, because the rows are yours: you already know what the answer should be, so a wrong query is obvious. Column types are INFERRED from the data and reported above the result, an empty cell is NULL rather than zero or an empty string, and every error names the exact column, token or limit that caused it.

Try it now

Worked example

Tables t — 7 row(s), 5 column(s) id (number) customer (text) region (text) amount (number, 1 NULL) status (text) t2 — 3 row(s), 2 column(s) region (text) manager (text) | region | manager | orders | total | +--------+---------+--------+-------+ | North | Kay | 1 | 240 | | East | Iris | 2 | 180.5 | | West | Jon | 1 | 80 | 3 row(s) returned. Result as CSV: region,manager,orders,total North,Kay,1,240 East,Iris,2,180.5 West,Jon,1,80

Learn the method

Inputs

Frequently asked

What are the tables called?

The CSV in the main box is `t` — you can also write `t1`. The second CSV box is `t2`. Both take a header row, and those headers are the column names; a blank header becomes c1, c2 and so on, and a duplicate header gets a _2 suffix. You can alias them like any SQL table, for example FROM t AS orders JOIN t2 AS regions ON orders.region = regions.region. A column name with spaces or punctuation can be written in "double quotes"; text values always use 'single quotes'.

How are column types decided, and what happens to empty cells?

A CSV carries no types, so each column is inferred: if every non-empty cell in it parses as a finite number, the column is a number column; otherwise it is text. The inference is printed above the result so it is never a mystery. An EMPTY CELL IS NULL — not 0 and not an empty string. So IS NULL finds it, = '' does not, COUNT(*) counts the row while COUNT(col) does not, and SUM, AVG, MIN and MAX skip it. That is the same rule a real database uses, and it is the single most common thing people get wrong when they move a spreadsheet into SQL.

What happens if I compare a number to a text value?

Nothing accidental. Values are ordered NULL first, then all numbers, then all text — the same order SQLite uses. So a number is never equal to a text value: on a number column, WHERE qty = '4' matches nothing and WHERE qty = 4 is the query you meant. Text comparison with = is case-sensitive; LIKE ignores ASCII letter case. Arithmetic never silently converts text either: '5' + 1 is NULL, not 6. And any comparison involving NULL is unknown, so the row is not returned — that is why WHERE amount <> 'paid' will not show you rows where amount is NULL.

Which SQL is deliberately NOT here?

There is no INSERT, UPDATE, DELETE, CREATE or DROP — this is a read-only query engine over what you pasted, so nothing can be modified or lost. There are also no subqueries, no UNION/INTERSECT/EXCEPT, no CASE expression, no window functions, no RIGHT/FULL/CROSS JOIN, no LIKE ... ESCAPE and no date functions. Each of those returns a specific message saying so, rather than a mystery failure. What is here is implemented properly rather than half-implemented.

Why did my query stop with a limit message?

Because a browser tab has to stay responsive. Each table reads at most 5,000 data rows and 200 columns; a query may be 8,000 characters; expressions may nest 32 levels of parentheses and 96 levels overall; a JOIN may test at most 1,000,000 row pairs and produce at most 20,000 rows; a GROUP BY may make 5,000 groups; and 2,000 result rows are shown. The JOIN limit is checked BEFORE the join is built — joining two 5,000-row tables would be 25,000,000 pairs, so it is refused instead of freezing the page. Every limit that trips says which limit it was.

What does the error message tell me?

The exact thing that failed. A misspelt column gives "unknown column `naem` — did you mean `name`?"; a column that exists in both tables gives an ambiguity error telling you to qualify it; an unclosed quote gives the character position where it opened; an aggregate in WHERE tells you to use HAVING. There is no generic "invalid query" anywhere in this tool.

Related Dev & Data tools