How to pretty print SQL
· 6 min read
ORMs and query logs emit SQL on a single line. A join across four tables with a couple of subqueries arrives as an 800-character wall of text, and you are expected to find the missing condition in it. Pretty printing — reformatting that line into indented, readable structure — takes a second and turns an unreadable statement into something you can reason about.
What a pretty printer actually does
It is not a search-and-replace on newlines. A proper SQL formatter tokenises the statement first, which is what lets it tell the difference between a keyword and a column that happens to be called order, and between a comma separating select-list items and one inside a function call. Once it has tokens, it applies structure:
- Each major clause —
SELECT,FROM,WHERE,GROUP BY,ORDER BY— starts a new line. - Select-list items and join conditions are indented under their clause.
- Subqueries and parenthesised expressions get a further level of indentation.
ANDandORbegin lines, so the shape of a boolean condition is visible at a glance.- Keyword casing is normalised, usually to uppercase.
Crucially, none of this changes what the query does. String literals, comments, and identifiers are preserved exactly; only the whitespace between tokens moves.
Why uppercase keywords still help
SQL is case-insensitive for keywords, so select and SELECT run identically. The convention of uppercasing keywords survives because it gives you a second visual channel: keywords in caps, your schema in lowercase, and suddenly you can see the skeleton of a statement without reading it word by word. In a query where a column is called from_account or a table is called order, that separation is the difference between a readable statement and a puzzle.
Dialects are not interchangeable
A formatter has to know which dialect it is reading, because the tokens differ in ways that break naive parsing:
- PostgreSQL uses
$1placeholders,::casts, and$$-quoted function bodies. - MySQL quotes identifiers with backticks and treats
#as a comment. - SQLite is permissive about quoting and accepts several placeholder styles.
- BigQuery adds backtick-quoted fully qualified names, array and struct literals, and
EXCEPTinsideSELECT *.
Feed BigQuery syntax to a strict standard-SQL formatter and it will either mangle the statement or refuse it. Picking the right dialect is usually the fix when output looks wrong.
Reading a formatted query
Once the statement is laid out, a few problems become visually obvious rather than analytically hard:
- An accidental cross join — a table in
FROMwith no matching condition — stands out because its line has noONbeneath it. - A misplaced
ORthat should have been parenthesised sits at the wrong indentation level. - Duplicated conditions from a query builder that appended the same filter twice line up underneath each other.
- A subquery that could be a join is suddenly a visible block rather than a fragment buried mid-line.
The bit worth thinking about: where the query goes
Production SQL is not neutral text. A query pulled from a slow-query log or an APM trace typically carries real table and column names — that is your schema — and frequently literal parameter values: email addresses, customer IDs, order totals, sometimes a hashed password in a WHERE clause. Pasting that into an online formatter ships your schema and a sample of production data to a third party, where it lands in request logs you have no way to inspect or delete.
For anyone under GDPR this is worth stating precisely: pasting customer data into a random web service is a transfer to a processor you have no agreement with. It is a compliance problem, not just an untidy habit.
KeepItLocally's SQL formatter pretty prints in your browser and never transmits the query. It handles standard SQL, PostgreSQL, MySQL, SQLite, and BigQuery, with keyword casing options. There is no backend to receive anything, and the Content-Security-Policy blocks third-party requests — open DevTools → Network and watch nothing leave the page, or disconnect entirely and keep formatting.
Quick reference
- Pretty printing never changes semantics — only whitespace and keyword case.
- Pick the right dialect before blaming the formatter.
- Uppercase keywords to separate SQL from your schema visually.
- Logged queries contain real data — format them locally.
Working with JSON in the same debugging session? The JSON formatter works the same way, in the browser.