Writing SQL People Can Actually Read
Formatting doesn't change what a query does — but it changes whether anyone can understand it, including you next month.
Why bother formatting SQL?
A database does not care how your SQL is laid out — extra spaces and line breaks are ignored, and a beautifully formatted query runs exactly as fast as the same query crammed onto one line. So formatting is entirely for humans. And that turns out to matter a lot: SQL is read far more often than it is written, in code reviews, debugging sessions, and when someone (often your future self) tries to understand why a report is wrong. A well-formatted query reveals its structure at a glance; a one-line tangle hides bugs in plain sight. Paste any query into theSQL Formatterto see the difference instantly.
The core conventions
Most SQL style guides converge on the same handful of rules, because they genuinely aid readability:
- Uppercase keywords.
SELECT,FROM,WHERE,JOINin capitals stand out against lowercasetableandcolumnnames, so the skeleton of the query pops. - One clause per line. Put each major clause — SELECT, FROM, WHERE, GROUP BY, ORDER BY — at the start of its own line, so the shape of the statement is obvious.
- One column per line in the SELECT list (for anything beyond two or three), which makes it easy to add, remove, and diff columns.
- Indent conditions. Line up
AND/ORunder the WHERE so the logic reads as a list. - Consistent case for identifiers. Pick lowercase (or snake_case) table and column names and stick to it.
Before and after
The value is easiest to see with an example. Here is a typical query as it often arrives:
select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=1 and u.created_at>'2024-01-01' group by u.id,u.name having count(o.id)>3 order by orders desc limit 10;
And the same query formatted:
SELECT u.id,
u.name,
COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = 1
AND u.created_at > '2024-01-01'
GROUP BY u.id,
u.name
HAVING COUNT(o.id) > 3
ORDER BY orders DESC
LIMIT 10;The second version is not shorter, but you can read it. The joins, the filters, the grouping and the limit each announce themselves, and if this query later returns wrong numbers, you can find the offending line in seconds rather than parsing a paragraph.
Readable joins and subqueries
Joins are where readability pays off most. Always state the join type explicitly (LEFT JOIN, INNER JOIN) rather than relying on commas in the FROM clause, and put the ON condition on the same line or just beneath its join so it is clear which tables are being connected and how. For subqueries, indent the inner query as a visual block so it reads as a nested unit rather than blurring into the outer query. Table aliases help too — a short, meaningful alias (u for users, o for orders) keeps column references concise, as long as they are consistent.
Formatting and version control
There is a practical, team-level reason to format consistently: diffs. When SQL lives in your codebase (in migrations, views, or query files), a consistent one-clause-per-line style means that changing a single condition produces a one-line diff, which is easy to review. An unformatted query, by contrast, turns any edit into a change to one enormous line, hiding what actually changed. Agreeing on a format — and running everything through a formatter — keeps reviews focused on the real change, not the whitespace.
When to minify
Formatting is for reading; occasionally you want the opposite. Minifying a query back to a single line is handy when embedding SQL in a string in application code, pasting it into a one-line config, or logging it compactly. The logic is identical either way, so the workflow is simple: keep your canonical SQL formatted and readable, and minify only at the moment you need a compact form. The SQL Formatter does both directions, so you can move between a readable query and a one-liner freely.
Formatting long WHERE clauses
Filters are where queries most often become unreadable, because conditions accumulate over time and nobody reformats. The convention that helps most is leading operators: put each AND or OR at the start of its line rather than trailing at the end of the previous one.
WHERE u.active = 1
AND u.created_at >= '2024-01-01'
AND u.country IN ('GB', 'IE')
AND u.deleted_at IS NULLTwo things follow from that layout. The operators line up in a column so you can see the logic at a glance, and — more practically — commenting out or removing a condition is a clean single-line change rather than an edit that strands a dangling AND on the line above. When you mix AND and OR, always add explicit parentheses and indent the grouped block; SQL's precedence rules mean a AND b OR c does not mean what many people assume, and a reader should never have to recall the precedence table to understand your filter.
Joins, subqueries and CTEs
Complex queries stay readable when their structure is visible. For joins, always write the type explicitly (INNER JOIN, LEFT JOIN) rather than comma-joining tables in the FROM clause, and keep each join with its own ON condition so the relationship between two tables reads as one unit. Table aliases should be short but meaningful, and used consistently — u for users throughout, never u in one place and usr in another.
For nested logic, common table expressions (the WITH clause) are usually more readable than inline subqueries, because they let you name each step and read the query top to bottom rather than inside out. A deeply nested subquery forces the reader to work from the middle outward; a CTE named active_users tells them what that block produces before they read it. The formatting rule is simple: give each CTE its own indented block, and put a blank line between them.
Naming that makes SQL readable
Formatting only goes so far if the identifiers fight you. A few naming conventions do a disproportionate amount of work for readability across a codebase.
| Convention | Why |
|---|---|
| snake_case identifiers | Avoids quoting; case handling differs between databases |
| Consistent table plurality | Pick users or user and never mix |
| Avoid reserved words | Columns named order or user need quoting forever |
| Explicit column lists | SELECT * hides intent and breaks on schema change |
The reserved-word point is worth emphasising because it interacts with formatting directly. A column genuinely named order will be uppercased by most formatters, since ORDER is a keyword — an unavoidable ambiguity that is best solved by renaming the column or quoting it consistently. Choosing identifiers that never collide with keywords saves you from a whole category of tooling friction.
Formatting is not optimisation
A point worth making explicitly: making a query readable and making it fast are entirely separate concerns. The database parses your SQL into an internal representation and hands it to a query planner, which decides how to execute it based on indexes, table statistics and its own cost model. Whitespace and capitalisation are discarded before any of that happens, so a beautifully formatted query and a one-line version produce byte-identical execution plans.
What formatting does do is make performance work possible. When a query is slow, you have to read it to find the culprit — a missing join condition producing a cartesian product, a function wrapped around an indexed column preventing index use, a subquery running per row. All of those are far easier to spot in a query whose structure is laid out clearly. Format for humans; then use EXPLAIN and the planner's output to actually optimise.
Fitting it into a workflow
- Format before committing. Run any SQL through a formatter before it lands in a migration, view or query file, so the repository stays consistent.
- Format inherited queries first. When debugging someone else's one-liner, reformat it before trying to understand it — the structure often reveals the bug immediately.
- Agree a house style. Any consistent convention beats a mix of three; the argument is less important than the agreement.
- Minify only at the boundary. Keep the readable version canonical and collapse to one line only when embedding in code or a config.
- Be careful with generated SQL. ORM output is often machine-shaped; format it to read it, but change the ORM call rather than the SQL.
Reading someone else's query
A large share of SQL work is understanding a query you did not write — a slow report, an inherited migration, something an ORM generated. Formatting it first is step one, but there is a reliable order for reading it afterwards that is much faster than starting at the top.
- Start with FROM and the joins. These tell you what data the query is actually built from — the shape of the result before any filtering.
- Then read WHERE. This tells you which rows survive, and is usually where the business logic and the bugs live.
- Then GROUP BY and HAVING. These reveal the granularity of the output — one row per user, per order, per day.
- Read SELECT last. Counter-intuitive, but the column list is the least informative part; it only makes sense once you know what a row represents.
This order mirrors how the database itself logically processes a query, which is why it feels so much clearer than reading top to bottom. If the query has CTEs, read each one in this same order before moving on to the next, treating each as a named table. Nine times out of ten, a query that looked impenetrable becomes obvious within a minute of formatting it and reading it in this sequence.
Formatting ORM-generated SQL
A great deal of the SQL developers actually read was written by an ORM rather than a person, and it tends to be brutal: single-line, heavily aliased with names like t0 and c1, and often wrapped in redundant subqueries. When a query from your logs is slow, formatting it is the essential first step — the structure that was invisible in one line usually makes the problem obvious, whether that is an unexpected join, a missing filter, or a subquery evaluated per row. Just remember that the fix belongs in the ORM call, not the SQL: reformat to understand, then change the code that generates it.
Comments: the part formatting can't do
Formatting reveals a query's structure, but not its reasoning. That gap is what comments fill, and complex SQL benefits from them more than most code because the "why" is so often invisible. SQL supports two forms: -- for a single line and /* … */ for a block.
The comments worth writing are the ones explaining a decision a reader would otherwise question. Why is there a magic number in a filter? Why does this join use a subquery instead of a direct condition? Why are certain rows deliberately excluded? A one-line note above an unusual WHERE clause saves the next person — often you — from either rewriting it "correctly" and breaking a report, or spending an hour reconstructing the reasoning from a ticket history. Conversely, do not comment what the SQL already says: -- select the users above a SELECT from users adds nothing but noise.
In short: formatting is documentation you get for free. It changes nothing about how the database executes your query, and everything about how quickly a human — reviewer, teammate, or your future self — can understand, trust and safely change it. Good SQL formatting costs nothing at runtime and pays off every time someone reads the query. Uppercase your keywords, give each clause its own line, and be consistent — or just paste your SQL into theSQL Formatterand let it do the tidying, entirely in your browser.