What's actually happening when a query runs
Write a query with SELECT at the top and WHERE in the middle — but that's not the order SQLite actually executes it in. The engine works through clauses in a fixed logical sequence, rebuilding and shrinking the row set at every step. The diagram above runs your exact query through sql.js — real SQLite compiled to WebAssembly — and shows the true intermediate result after each stage. Click any stage to zoom in and see its exact underlying SQL and full row grid.
FROM & JOIN
Execution starts here, not at SELECT. The engine loads the base table(s) and, if a JOIN is present, matches rows between tables on the join condition before anything else happens.
WHERE
Every row from FROM/JOIN is tested against the condition. Rows that evaluate true continue forward; rows that evaluate false are discarded permanently for the rest of this query — you can see them peel off into the faded strip below each WHERE stage.
GROUP BY
Surviving rows are bucketed into groups sharing the same value(s) in the GROUP BY column(s). From here on, only grouped columns or aggregates like COUNT(), SUM(), AVG() can appear in SELECT.
HAVING
HAVING filters entire groups, often by an aggregate condition (e.g. HAVING COUNT(*) > 3) — which is why aggregates can't be used in WHERE: groups don't exist yet at that point.
SELECT & DISTINCT
Only now does the engine project down to the columns/expressions you asked for. DISTINCT, if present, collapses duplicate resulting rows at this same stage.
ORDER BY
With the final row shape decided, the engine sorts the result — which is why you can ORDER BY a column alias defined moments earlier in SELECT.
LIMIT / OFFSET
Finally, the sorted result is trimmed to the requested window — always the very last step, always applied to the fully filtered, grouped, and sorted output.
sql.js (SQLite → WebAssembly). No server, no scripted data.