SQL formatting best practices for readable queries

Formatting is not cosmetic in SQL. A badly laid out query hides accidental cartesian products and misplaced filters — a well laid out one shows them at a glance.

By Sk Md Rakib · Published · Updated · 10 min read

Why formatting is a correctness concern

SQL is declarative and dense: a single statement can express a join across six tables, three filters, a grouping and a window function. Compressed onto one line, the structure disappears, and reviewers stop reading it properly — which is precisely how a missing join condition reaches production.

Formatted SQL makes structural mistakes visible. When each join sits on its own line with its condition attached, a join without an ON clause is impossible to miss. When each WHERE condition has its own line, a filter accidentally applied inside a subquery instead of outside it stands out.

Keyword casing and layout

Pick one keyword casing convention and enforce it. Uppercase keywords with lowercase identifiers is the most widely used because it makes the skeleton of the statement scannable at a glance. Whichever you choose, mixed casing inside one codebase is the only genuinely wrong answer.

Put major clauses — SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY — at the start of their own line at a consistent indentation level, and indent subquery bodies one level deeper. Nesting depth should be visible from the left margin alone.

Always use explicit JOIN syntax

Never write the implicit comma join form, FROM a, b WHERE a.id = b.a_id. It scatters join conditions among filter conditions in the WHERE clause, so omitting one produces a silent cartesian product rather than an error. On tables of any size that is a query that appears to hang.

Explicit JOIN ... ON keeps the relationship next to the table it relates, and makes the join type deliberate. Write LEFT JOIN when you mean it and INNER JOIN when you mean that, rather than relying on a WHERE clause to convert one into the other by accident.

Alias every table, and choose aliases with meaning

In any multi-table query, alias every table and qualify every column reference. Unqualified columns become ambiguous as the query grows, and error messages about ambiguity are far less helpful than the discipline that prevents them.

Choose short but meaningful aliases — usr, ord, prod — rather than a, b, c. Single letters force the reader to scroll back to the FROM clause to decode every column reference, which is exactly the cognitive cost formatting is supposed to remove.

  • One column per line in long SELECT lists.
  • One condition per line in WHERE, with the AND or OR leading the line.
  • Join condition on the same line as, or immediately under, its JOIN.
  • Subqueries and CTE bodies indented one level from their keyword.

Prefer CTEs over deep nesting

A query nested four subqueries deep is read from the inside out, which is the opposite of how anyone reads anything. Common table expressions let you name each step and compose them top to bottom, so the query reads as a sequence of named transformations.

Naming is the real benefit: monthly_totals and active_users document intent in a way that an anonymous derived table never can. Check your engine's optimisation behaviour for materialised CTEs on hot paths, but for reporting and analysis queries the readability win is almost always worth it.

Enforce it automatically

Formatting conventions that rely on discipline decay. Put a formatter in the loop instead: format before committing, and if your language has one, add a SQL linter to CI so style stops being a code-review discussion.

Our SQL Formatter applies consistent casing, indentation and line breaks across the common dialects, entirely in your browser. That matters for SQL specifically, because queries routinely contain table names, column names and literal values from production systems — none of which should be pasted into a server-side tool.

Formatting for review, and for the query planner

Formatting is not only cosmetic; it changes what a reviewer notices. A predicate hidden at the end of a long line is easy to miss, and a missing join condition looks like ordinary text until the query returns a cartesian product in production. Putting each join and each predicate on its own line, with the keyword leading, makes an omission visible as a shape rather than something you have to read for.

Consistency matters more than the particular style you choose. Pick one convention — keyword case, indent width, whether commas lead or trail, how far to indent subqueries — write it down, and enforce it with a formatter in your editor or a pre-commit hook. Reformatting on save also keeps diffs meaningful: a review that shows one changed predicate instead of forty realigned lines is a review that actually happens.

Long queries usually want restructuring, not just reindenting. Named common table expressions let you give each step a meaningful name and read the query top to bottom, and most modern planners treat them as optimisation fences only when you ask for materialisation. Deeply nested subqueries, by contrast, force a reader to work inside out and hide where a filter is applied.

Keep generated and hand-written SQL separate. Output from an ORM or a query builder should be formatted for reading while you debug it, then discarded — do not paste a reformatted version back into application code, because the next build will regenerate it and your changes will vanish. Store hand-written queries in versioned files where they can be reviewed, formatted and tested like any other source.

  • One join or predicate per line so an omission is visually obvious.
  • Agree one style and automate it; never argue about it in review.
  • Prefer named CTEs over deep nesting for multi-step queries.
  • Format generated SQL to read it, not to check it back in.

// tools referenced in this guide

// more guides