Structure your queries into named steps with WITH and chain CTEs like a pipeline of transformations.
Open this lesson in KodokonA subquery nested three levels deep reads from the inside out: unreadable and impossible to revisit six months later. The CTE (Common Table Expression, the WITH clause) flips the reading order: you name each intermediate step just as you would name a function, and the query reads top to bottom. Load this dataset into sqliteonline.com or sqlite3.
CREATE TABLE payments (
id INTEGER PRIMARY KEY,
customer TEXT NOT NULL,
paid_at TEXT NOT NULL,
amount REAL NOT NULL
);
INSERT INTO payments (customer, paid_at, amount)
VALUES
('alice', '2026-01-10', 120.0),
('bruno', '2026-01-22', 80.0),
('alice', '2026-02-05', 60.0),
('chloe', '2026-02-18', 200.0),
('bruno', '2026-03-03', 40.0),
('alice', '2026-03-15', 90.0),
('chloe', '2026-03-28', 150.0),
('bruno', '2026-04-09', 300.0);Basic syntax: WITH name AS (SELECT ...) followed by the main query, which uses name like an ordinary table. Here, we compute monthly revenue by extracting the month from the date with substr (SQLite dates are ISO 8601 text, an engine choice rather than a standard).
WITH monthly AS (
SELECT substr(paid_at, 1, 7) AS month,
SUM(amount) AS revenue
FROM payments
GROUP BY month
)
SELECT * FROM monthly ORDER BY month;The real power comes from chaining: a single WITH, several CTEs separated by commas, each able to read those defined before it. You build a pipeline: aggregate, compute a statistic, compare. Here, we look for the months whose revenue exceeds the monthly average (expected: 2026-03 and 2026-04, the average being 260).
WITH monthly AS (
SELECT substr(paid_at, 1, 7) AS month,
SUM(amount) AS revenue
FROM payments
GROUP BY month
),
stats AS (
SELECT AVG(revenue) AS avg_revenue
FROM monthly
)
SELECT m.month, m.revenue,
ROUND(s.avg_revenue, 1) AS avg_all
FROM monthly AS m
CROSS JOIN stats AS s
WHERE m.revenue > s.avg_revenue
ORDER BY m.month;