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 five 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.
Open the SQL practice panel: 32 exercises from a first SELECT to recursive CTEs, checked by comparing your result with a reference answer.
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
SQL playground online, explained
SQL Playground is an online SQL editor that runs a real database engine inside your browser tab: SQLite 3.45.2, compiled to WebAssembly by sql.js 1.10.3. There is no database server behind it. Your queries, your .sqlite files and your spreadsheets are processed on your own machine and are never uploaded. Use it as a SQL sandbox for learning, to test a query before it goes into your app, or to explore a database file or CSV export someone sent you without installing anything.
It opens with the Chinook music-store sample already loaded, so your first query can run within seconds of the page loading.
What runs your queries
The engine is the official SQLite C library compiled to WebAssembly by the open-source sql.js project. The page loads two files from cdnjs.cloudflare.com: sql-wasm.js (about 50 KB) and sql-wasm.wasm (about 650 KB). After that every statement runs locally. Run SELECT sqlite_version(); to check the version yourself; the first query tab does exactly that.
The database lives in memory. Opening a file reads the whole thing into RAM, and changes stay there until you download the database or the playground saves it in your browser (see below). This build includes JSON functions (with -> and ->>), window functions, CTEs, math functions such as sqrt(), STRICT tables, RETURNING, FULL OUTER JOIN and FTS3/FTS4 full-text search. It does not include FTS5, the R*Tree module, generate_series or a REGEXP function, so those statements fail with "no such module" or "no such function".
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 29 ready-made queries: eight for Chinook and seven each for 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.
The sample databases
The four samples are created by SQL inside the page, so they load instantly and work offline once the engine is loaded. They are small on purpose: you can see every row and check an answer by eye.
| Sample | Tables | Rows | Good for |
|---|---|---|---|
| Chinook | 7 tables (artists, albums, tracks, genres, customers, invoices, invoice_items) + a top_customers view | 83 | joins across a chain of foreign keys |
| Northwind | 6 tables (categories, suppliers, products, customers, orders, order_details) | 71 | order totals, HAVING, LEFT JOIN for missing rows |
| Employees | 2 tables (departments, employees) + 1 index | 20 | first SELECTs, grouping, subqueries |
| Shop | 5 tables (customers 320, products 29, orders 2,600, order_items 4,056, reviews 700) + a monthly_revenue view + 3 indexes | 7,705 | window functions, CTEs, charts, query plans |
Chinook and Northwind are trimmed-down versions of the well-known teaching databases, not the full downloads: Chinook here has 19 tracks rather than several thousand. Shop is generated in your browser from a fixed random seed, so everyone gets exactly the same 7,705 rows and the exercises always have the same answers.
Query CSV, JSON and Excel files with SQL
You don't need a database file to start. Drop a .csv, .tsv, .txt, .json, .ndjson/.jsonl or Excel file (.xlsx, .xlsm, .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;
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. Excel files need the SheetJS reader, which is only downloaded the first time you open a spreadsheet.
Export formats
A result can be saved as CSV, JSON (an array of objects), a Markdown table, an Excel-ready CSV (UTF-8 with a byte-order mark and semicolons, so Excel opens it with accents and columns intact) or a file of INSERT statements for a table name you choose. You can also copy the grid and paste it into a spreadsheet. A chart downloads as SVG, and β¬ db saves the whole database as a standard .sqlite file that opens in DB Browser for SQLite, the sqlite3 command line or any SQLite library.
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 learners 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 |
The biggest conceptual difference is typing. SQLite uses type affinity: a column declared INTEGER will still accept the text 'abc' unless the table is created STRICT. MySQL and PostgreSQL reject or convert such values. SQLite also has no separate DATE type: dates are stored as ISO text such as 2026-10-13 and handled with date(), datetime(), strftime() and julianday(). And foreign keys are only enforced after PRAGMA foreign_keys = ON;.
Which SQL it speaks
The engine supports 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. 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 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.
Saving your work, file sizes and limits
With keep my databases in this browser ticked (the default), a database you have changed is saved a few seconds later to your browser's IndexedDB storage (a database called jb-sql-playground) together with its query tabs, and listed under recent. The six most recent are kept; untouched samples and empty databases are not saved, and databases over 64 MB are not autosaved (download them with β¬ db). Untick the box and nothing is kept after you close the tab. Your last 50 queries (under Query History), your practice progress and the recent list are kept in localStorage. Clearing your browser's site data removes all of it.
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 (CORS). Raw GitHub files and most open-data portals do.
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.
Privacy: what stays on your device and what does not
Your tables, files and query results never leave the browser: the page has no server to send them to. It does make a few requests of its own, and you should know what they are:
- cdnjs.cloudflare.com: the sql.js engine on every visit, and the SheetJS spreadsheet reader (
xlsx0.18.5) the first time you open an Excel or ODS file. These are plain downloads; none of your data is attached. - The URL you fetch: fetch and
?url=links download the file straight from that host, which sees an ordinary request from your browser. - Google Fonts for the JetBrains Mono font, and Simple Analytics for a cookie-free page-view count.
A π share link contains the text of your query in the URL (and the sample name, if you used an unchanged sample) but never your data, so check the SQL itself for anything private before you send it.
Practise SQL with 32 exercises
The practice panel has 32 exercises on the sample databases: 10 beginner, 15 intermediate and 7 advanced, 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; where the task asks for a sort order, the order is checked too. Hints and solutions are one click away, and your progress stays in this browser. A link such as ?practice=12 opens one exercise directly.
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 |