Free SQL template
Schema design for a new feature
Tables, keys and constraints for a new feature, sample rows, and the queries the feature needs.
It opens with this template loaded and runs in your browser. You don't need an account to use it; a free account saves your work and exports it.
The job it does
When a feature needs new tables: agree the design with a working schema and its main queries before writing app code. It comes with its own checks, so after you change it you can see at a glance that it still does the job.
How to use it with your own data
Import an existing schema (Database panel → Import a schema) and add the new tables next to it.
What's inside
This is the template exactly as the SQL Lab opens it, read-only. Open it to run it, change it and export it.
- Runs on an empty database: setup.sql builds the tables
- 3 tabs
- PostgreSQL in your browser
A new feature needs new tables. Agree the design with a working schema: tables, keys and constraints in setup.sql, a few sample rows, and the queries the feature will run.
This one is click & collect: customers book a pickup slot, and a slot holds a limited number of orders. The criteria pin the answer the booking screen must get from the sample rows. Change the tables to your feature, then make the queries answer its questions.
Your own data: import an existing schema (Database → Import a schema) and add the new tables next to it.
- Has the columns slot, store, starts_at, free_places
- Equals the expected table, in order
- Does not change any data
-- New feature: click & collect. A customer books a pickup slot at a store;
-- each slot holds a limited number of orders. Keys and constraints keep the data honest.
CREATE TABLE stores (
id int PRIMARY KEY,
name text NOT NULL UNIQUE
);
CREATE TABLE pickup_slots (
id int PRIMARY KEY,
store_id int NOT NULL REFERENCES stores (id),
starts_at timestamp NOT NULL,
capacity int NOT NULL CHECK (capacity > 0),
UNIQUE (store_id, starts_at)
);
CREATE TABLE pickup_orders (
id int PRIMARY KEY,
slot_id int NOT NULL REFERENCES pickup_slots (id),
customer text NOT NULL,
status text NOT NULL DEFAULT 'booked' CHECK (status IN ('booked', 'collected', 'cancelled')),
booked_at timestamp NOT NULL DEFAULT now()
);
-- the "free places" query reads live orders by slot
CREATE INDEX pickup_orders_live_by_slot ON pickup_orders (slot_id) WHERE status <> 'cancelled';
-- sample rows, so the queries have something to show
INSERT INTO stores VALUES (1, 'Riverside'), (2, 'Hilltop');
INSERT INTO pickup_slots VALUES
(1, 1, '2026-03-02 09:00', 3),
(2, 1, '2026-03-02 10:00', 3),
(3, 2, '2026-03-02 09:00', 2);
INSERT INTO pickup_orders (id, slot_id, customer, status) VALUES
(1, 1, 'Nadia', 'booked'),
(2, 1, 'Imran', 'booked'),
(3, 1, 'Leila', 'cancelled'),
(4, 3, 'Ravi', 'booked'),
(5, 3, 'Sara', 'collected');-- What the booking screen needs: free places per slot (cancelled orders do not count).
SELECT s.id AS slot,
st.name AS store,
s.starts_at,
s.capacity - count(o.id) AS free_places
FROM pickup_slots s
JOIN stores st ON st.id = s.store_id
LEFT JOIN pickup_orders o ON o.slot_id = s.id AND o.status <> 'cancelled'
GROUP BY s.id, st.name, s.starts_at, s.capacity
ORDER BY s.starts_at, st.name;-- What the store needs: tomorrow's pickups, by slot.
SELECT s.starts_at, o.customer, o.status
FROM pickup_orders o
JOIN pickup_slots s ON s.id = o.slot_id
WHERE s.store_id = 1 AND o.status = 'booked'
ORDER BY s.starts_at, o.customer;The lesson behind it
Learn why it works, with a podcast and a quiz for each lesson.
More SQL templates
Monthly sales report
Revenue, orders and average order value per month, with the change on the month before.
Cohort retention
Customers grouped by the month of their first order: how many come back in each later month.
Data-quality checks
Duplicates, missing values, orphans and impossible values, each as one row with a count.