SQL Playground — run SQLite online, in your browser
- Beginner
- ✓1Highest earners firstemployees
- ✓2Former employeesemployees
- ✓3Hired recentlyemployees
- ✓4Headcount per departmentemployees
- ✓5Average salaryemployees
- ✓6Employees with their departmentemployees
- ✓7Albums by one artistchinook
- ✓8Stock problemsnorthwind
- ✓9Orders by statusshop
- ✓10Cheap teashop
- Intermediate
- ✓11Big-budget salariesemployees
- ✓12Above averageemployees
- ✓13Small departmentsemployees
- ✓14Tracks per genrechinook
- ✓15The three longest trackschinook
- ✓16Never soldchinook
- ✓17Revenue per countrychinook
- ✓18Order totalsnorthwind
- ✓19Repeat customersnorthwind
- ✓20Unsold productsnorthwind
- ✓21Best sellersshop
- ✓22Revenue in 2024shop
- ✓23Silent customersshop
- ✓24First order from Belgiumshop
- ✓25Price riseshop
- Advanced
- ✓26Average order value per channelshop
- ✓27Running totalshop
- ✓28Month-over-month changeshop
- ✓29Best seller per categoryshop
- ✓30Ratings per categoryshop
- ✓31Top 10 customersshop
- ✓32Counting with recursionemployees
Practise SQL on real sample data
Pick an exercise. The matching sample database opens in the editor above, in its own tab. Write your query there and press ✓ check my answer — it runs your SQL and a reference solution on a fresh copy of the data and compares the results, so any correct query passes, not just one exact answer.
They start with SELECT, WHERE and ORDER BY, move on to joins, GROUP BY, HAVING and subqueries, and end with CTEs, window functions and recursive queries. Your progress is remembered in this browser.
How to use the SQL playground
From a blank page to a query result, chart or export in four steps — no install, no account, nothing uploaded.
Load a sample (Chinook, Northwind, Employees or Shop), drop your own .sqlite, CSV, JSON or Excel file, or start an empty database.
Autocomplete knows your tables and columns. Stuck? 📚 examples has ready-made queries for every sample.
⌘/Ctrl + Enter runs the script; each result gets a tab you can sort, chart, or check with 🧭 plan.
Export CSV, JSON, Markdown, Excel or INSERTs, download the .sqlite, or 🔗 share a link that reruns the query.
Comfortable for one-liners and long scripts alike.
- Autocomplete
- tables, columns,
alias.suggestions - Format
- one clause per line, keywords upper-cased
- Tabs
- several queries side by side
- Errors
- “did you mean…?” and dialect hints
Files become real SQLite tables you can join.
- Files
- .sqlite .db .csv .tsv .json .xlsx .ods
- URL
- fetch a file, or link with
?url= - Types
- INTEGER, REAL or TEXT detected per column
- Grid
- edit rows, add or rename columns visually
Tools that explain a database and a query.
- Schema
- tables, views, indexes, row counts
- Diagram
- ER diagram drawn from foreign keys
- Plan
- EXPLAIN QUERY PLAN as a tree
- Chart
- bar, line or area, download as SVG
32 exercises checked against a reference answer.
- Levels
- first SELECT to recursive CTEs
- Checking
- any correct query passes
- Help
- hints and solutions on request
- Progress
- saved in this browser
A free online SQL playground that runs real SQLite
SQL Playground is an online SQL editor that runs a real database engine — SQLite 3.45, compiled to WebAssembly by sql.js — inside your browser tab. There is no server behind it: your queries, your .sqlite files and your spreadsheets are processed on your own machine, and nothing is uploaded. That makes it a quick SQL sandbox for learning, a place to test a query before it goes into your app, and a private way to explore a database file or a CSV export someone sent you.
It opens with the Chinook music-store sample already loaded, so you can run a query within seconds. Switch to Northwind, a small Employees database, or Shop — a generated e-commerce database with about 7,700 rows of customers, orders, order lines and reviews, big enough for window functions and charts to be interesting.
What you can do in the playground
- Write SQL comfortably — syntax highlighting, autocomplete for your own tables and columns, bracket pairing, auto-indent, several query tabs and a one-click SQL formatter.
- See every result — run a whole script and each
SELECTgets its own result tab; sort columns, copy the grid into Excel or Google Sheets, or export CSV, JSON, Markdown, Excel-ready CSV orINSERTstatements. - Chart a result — switch any result to a bar, line or area chart and download it as SVG.
- Understand a schema — the schema browser lists tables, views, indexes and row counts, and ◇ diagram draws an ER diagram from the foreign keys.
- Tune a query — 🧭 plan shows SQLite's
EXPLAIN QUERY PLANas a tree and flags full table scans. - Fix errors faster — typos in table or column names get “did you mean …?” suggestions, and common MySQL, PostgreSQL and SQL Server syntax gets the SQLite equivalent.
- Edit without SQL — a spreadsheet-style grid for table data, plus buttons to create, rename and drop tables and columns.
- Keep your work — databases you change are kept in this browser (IndexedDB) and listed under recent; turn it off with one checkbox, or download a real
.sqlitefile. - Example queries per sample — picking a sample database loads a query written for it, and 📚 examples lists seven or more ready-made queries for each of Chinook, Northwind, Employees and Shop.
- Share a query — 🔗 share copies a link that opens the same sample database and runs your query. Links never contain your own data.
Query CSV, JSON and Excel files with SQL
You don't need a database file to start. Drop a .csv, .tsv, .json, .ndjson or Excel file (.xlsx, .xls, .ods) onto the page, or paste a URL and press fetch. The importer detects the delimiter and column types, shows a preview, and creates a real table — for workbooks you pick the sheet. Import several files and JOIN them in one query, then export the answer. Values with leading zeros, such as postcodes and product codes, stay text so nothing is silently changed.
- Drop the file on the page (or use ⇪ import).
- Check the table name and the detected column types, then press ✓ import table.
- Query it:
SELECT * FROM my_file WHERE amount > 100 ORDER BY amount DESC;
Example SQL queries to try
Each example opens the right sample database and runs the query, so you can change it and see what happens.
| Technique | Query | |
|---|---|---|
| Filter and sort | SELECT name, category, price FROM products WHERE price > 25 ORDER BY price DESC; | ▶ run |
| Join two tables | SELECT al.Title, ar.Name AS artist FROM albums al JOIN artists ar ON ar.ArtistId = al.ArtistId; | ▶ run |
| Group and aggregate | SELECT country, COUNT(*) AS customers FROM customers GROUP BY country ORDER BY customers DESC; | ▶ run |
| Filter groups with HAVING | SELECT CustomerID, COUNT(*) AS orders FROM orders GROUP BY CustomerID HAVING COUNT(*) > 1; | ▶ run |
| Subquery | SELECT name, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees); | ▶ run |
| Common table expression | WITH totals AS (SELECT order_id, SUM(quantity * unit_price) AS value FROM order_items GROUP BY order_id) SELECT ROUND(AVG(value), 2) AS avg_order FROM totals; | ▶ run |
| Window function: running total | SELECT month, revenue, ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_total FROM monthly_revenue; | ▶ run |
| Window function: rank | SELECT name, dept_id, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank_in_dept FROM employees; | ▶ run |
| Dates | SELECT strftime('%Y', order_date) AS year, COUNT(*) AS orders FROM orders GROUP BY year; | ▶ run |
| CASE expression | SELECT name, price, CASE WHEN price < 10 THEN 'budget' WHEN price < 50 THEN 'mid' ELSE 'premium' END AS tier FROM products; | ▶ run |
| JSON functions | SELECT json_object('name', name, 'salary', salary) AS doc FROM employees LIMIT 5; | ▶ run |
| Recursive CTE | WITH RECURSIVE d(day) AS (SELECT date('2026-01-01') UNION ALL SELECT date(day, '+1 day') FROM d WHERE day < '2026-01-07') SELECT day, strftime('%w', day) AS weekday FROM d; | ▶ run |
SQLite cheat sheet
The statements you reach for most often, in SQLite syntax. Paste any of them into an empty database to try them.
| Task | SQL |
|---|---|
| Create a table | CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT NOT NULL, born DATE); |
| Insert rows | INSERT INTO people (name, born) VALUES ('Ada', '1815-12-10'), ('Alan', '1912-06-23'); |
| Update rows | UPDATE people SET name = 'Ada Lovelace' WHERE id = 1; |
| Upsert | INSERT INTO people (id, name) VALUES (1, 'Ada') ON CONFLICT(id) DO UPDATE SET name = excluded.name; |
| Delete rows | DELETE FROM people WHERE born < '1900-01-01'; |
| Return changed rows | DELETE FROM people WHERE id = 2 RETURNING *; |
| Add a column | ALTER TABLE people ADD COLUMN email TEXT; |
| Index a column | CREATE INDEX idx_people_name ON people(name); |
| List tables | SELECT name FROM sqlite_master WHERE type = 'table'; |
| Describe a table | PRAGMA table_info(people); |
| Today / now | SELECT date('now'), datetime('now', 'localtime'); |
| Days between dates | SELECT julianday('2026-12-25') - julianday('2026-09-17') AS days; |
| Concatenate text | SELECT 'Hello, ' || 'world' AS greeting; |
| Aggregate into a list | SELECT group_concat(name, ', ') FROM people; |
| Enforce foreign keys | PRAGMA foreign_keys = ON; |
SQLite vs MySQL vs PostgreSQL: differences that trip people up
Most everyday SQL works everywhere, but a few things differ. If you are practising for MySQL or PostgreSQL, these are the spots to watch.
| SQLite | MySQL | PostgreSQL | |
|---|---|---|---|
| Auto-increment key | INTEGER PRIMARY KEY | INT AUTO_INCREMENT | GENERATED … AS IDENTITY / SERIAL |
| Current time | datetime('now'), CURRENT_TIMESTAMP | NOW() | now() |
| Join strings | a || b, concat(a, b) | CONCAT(a, b) | a || b, concat(a, b) |
| Paging | LIMIT 10 OFFSET 20 or LIMIT 20, 10 | LIMIT 10 OFFSET 20 or LIMIT 20, 10 | LIMIT 10 OFFSET 20 |
| Case-insensitive match | LIKE (ASCII letters) | LIKE (default collations) | ILIKE |
| Booleans | integers 0 / 1 (TRUE and FALSE are aliases) | TINYINT(1) / BOOLEAN alias | real BOOLEAN type |
| Column types | flexible (type affinity), STRICT tables optional | enforced | enforced |
| Format a date | strftime('%Y-%m', d) | DATE_FORMAT(d, '%Y-%m') | to_char(d, 'YYYY-MM') |
| Upsert | ON CONFLICT(col) DO UPDATE | ON DUPLICATE KEY UPDATE | ON CONFLICT (col) DO UPDATE |
| Aggregate into a string | group_concat(x, sep), string_agg(x, sep) | GROUP_CONCAT(x SEPARATOR sep) | string_agg(x, sep) |
| FULL OUTER JOIN | yes (since 3.39) | no | yes |
| RETURNING | yes (since 3.35) | no | yes |
This build of SQLite includes JSON functions (including -> and ->>), window functions, CTEs, math functions such as sqrt(), and FTS3/FTS4 full-text search. It does not include FTS5, the R*Tree module, generate_series or a REGEXP function.
Your data: saving, files and limits
Everything runs in this browser tab. Files you open are read with the browser's File API and handed to the in-memory SQLite engine; the page only loads the engine itself (and, when you open a spreadsheet, the SheetJS reader) — your tables never travel with those requests. With keep my databases in this browser ticked, which is the default, a database you change is saved a few seconds later to IndexedDB and listed under recent with its query tabs; the six most recent are kept, untouched samples are not. Your last 50 queries are listed under Query History with their result and duration.
Because SQLite loads the whole database into memory, a few hundred MB is fine on a modern laptop, while multi-gigabyte files are better served by a desktop app such as DB Browser for SQLite. A file fetched from a URL is downloaded by your browser, so the host must allow cross-origin requests — raw GitHub files and most open-data portals do. When importing, a column becomes INTEGER if every value is a whole number, REAL if all are numeric and TEXT otherwise; postcodes, phone numbers and values over 15 digits stay TEXT, empty cells become NULL, and Excel dates arrive as YYYY-MM-DD text that sorts correctly. Untick detect column types to keep everything as text, or fix a column later with CAST.
There is no undo for data: DELETE, DROP TABLE and grid edits apply immediately. Wrap experiments in BEGIN; … ROLLBACK; or download a copy with ⬇ db first — the download keeps the connection open, so settings such as PRAGMA foreign_keys stay as they were. Grid edits run parameterised UPDATE, INSERT and DELETE statements, so a value like O'Brien is stored as text and never executed. Tables created WITHOUT ROWID can be changed with SQL but not in the grid.
Which SQL it speaks
The engine is SQLite 3.45: joins of every kind including RIGHT and FULL OUTER JOIN, CTEs and WITH RECURSIVE, window functions, RETURNING, upserts, JSON and math functions, PRAGMA and ATTACH. Paging works with LIMIT 10 OFFSET 20 and the MySQL-style LIMIT 20, 10. PRIMARY KEY, NOT NULL, UNIQUE, CHECK and DEFAULT are enforced as usual, but foreign keys only after PRAGMA foreign_keys = ON;. PostgreSQL-only syntax such as ILIKE, :: casts and JSONB is not available — the error message suggests the SQLite equivalent. The fundamentals you practise here (SELECT, joins, grouping, subqueries, CTEs, window functions) carry straight over to MySQL and PostgreSQL; the dialect table above lists what differs.
The whole editor runs as a script: statements execute in order and each one that returns rows gets its own result tab, while ▶ selection runs only the highlighted part. The layout works on phones and tablets too, though longer queries are nicer with a keyboard. It is free, with no account and no ads.
Practise SQL with 32 exercises
The practice panel has 32 exercises on the sample databases, from a first SELECT to window functions and recursive CTEs. Your query is checked by comparing its result with a reference solution on a fresh copy of the data, so any correct approach passes. Hints and solutions are one click away, and your progress stays in this browser.
Keyboard shortcuts
⌘/Ctrl + Enter | Run the editor |
⌘/Ctrl + Shift + Enter | Run the selected text only |
⌘/Ctrl + Shift + F | Format SQL (the selection, or everything) |
⌘/Ctrl + Space | Open autocomplete |
Tab | Accept a suggestion, or indent two spaces |
More developer tools on this site: the KQL example generator, the regex tester, the notepad and the log explorer.