Skip to content

Readable SQL — a practical style guide for formatting queries

How to lay out SELECT, JOIN, WHERE and CTEs so queries are easy to read and review, why the dialect matters when formatting, and the habits that prevent bugs.

By Felipe YamaokaPublished November 5, 20265 min read

The one-line query in the log

Sooner or later you have to debug a query like this one, copied from an ORM log or a monitoring tool:

sql
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 o.created_at >= '2026-01-01' group by u.id, u.name having count(o.id) > 5 order by orders desc limit 10

It works, but reading it means scanning back and forth to find where the join condition ends and the filter begins. The same query, formatted:

sql
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 o.created_at >= '2026-01-01'
GROUP BY
  u.id,
  u.name
HAVING
  count(o.id) > 5
ORDER BY
  orders DESC
LIMIT
  10

Now each clause has its own block and each condition its own line. You can see at a glance that the filter on o.created_at sits in WHERE, which quietly turns the LEFT JOIN into an inner join for users without recent orders. That kind of bug hides in one-line SQL and jumps out in formatted SQL. This guide covers the conventions that make that possible.

Formatting changes layout, not meaning

A formatter only changes whitespace, line breaks and, if you ask, the case of keywords. The tokens and their order stay the same, so the formatted query returns exactly the same result. That makes formatting safe to apply to any query, including ones you do not fully understand yet, and it makes code review diffs about logic instead of spacing.

The conventions that matter most

There is no single official SQL style, but most guides agree on a few rules:

  1. One clause per block. SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY and LIMIT start at the left margin, and their content is indented below.
  2. One column per line in SELECT. Adding, removing or commenting out a column becomes a one-line change, and diffs stay small.
  3. One condition per line, with the operator first. Starting lines with AND and OR makes the logic visible and easy to comment out.
  4. Joins with their condition on the same line. LEFT JOIN orders o ON o.user_id = u.id keeps each relationship readable as a unit.
  5. Consistent keyword case. Uppercase keywords (SELECT, FROM) are the most common convention because they separate the language from your table and column names. Lowercase is also fine; mixing both is what hurts.
  6. Meaningful aliases. u for users and o for orders are fine in a short query. In a long one, customers c and invoices i beat t1 and t2.

Indentation size is a matter of taste: two spaces, four spaces or tabs all work, as long as the whole project uses the same one.

Subqueries and CTEs

Nested subqueries are where readability usually collapses. A common table expression (CTE), introduced with WITH, lets you name an intermediate result and read the query from top to bottom:

sql
WITH
  recent AS (
    SELECT
      user_id,
      sum(total) AS spent
    FROM
      orders
    WHERE
      created_at >= '2026-01-01'
    GROUP BY
      user_id
  )
SELECT
  u.name,
  r.spent
FROM
  users u
  JOIN recent r ON r.user_id = u.id
WHERE
  r.spent > 1000
ORDER BY
  r.spent DESC

Each CTE is indented like a query of its own. A good name, such as recent here, documents what the step does better than a comment would.

Why the dialect matters

SQL looks the same everywhere until it does not. Each database has its own syntax for quoting identifiers, placeholders and some functions, and a formatter has to know which one you use to read the query correctly:

  • SQL Server quotes identifiers with brackets: [dbo].[users].
  • MySQL wraps identifiers in backticks, the same character used for code in Markdown.
  • PostgreSQL uses double quotes for identifiers and $1, $2 for query parameters.
  • SQLite accepts several styles for compatibility.

Feed select [id] from [dbo].[users] to a formatter set to standard SQL and it fails at the first bracket; set it to SQL Server (T-SQL) and it formats cleanly. The SQL Formatter on this site supports standard SQL, PostgreSQL, MySQL, SQL Server and SQLite, and when a query cannot be read it shows the line and column of the problem, such as an unclosed quote in where name = 'Ana.

Habits that prevent bugs

Formatting is about reading, but a few writing habits go together with it:

  • List columns instead of SELECT * in application code. A new column in the table should not silently change what your code receives.
  • Qualify columns with the table alias in any query with a join. o.created_at cannot become ambiguous when someone adds a created_at to another table.
  • Put join conditions in ON and filters in WHERE, and think twice when a WHERE condition references the optional side of a LEFT JOIN, as in the example at the top.
  • Use parameters, never string concatenation, for values that come from users. It prevents SQL injection and lets the database reuse query plans.
  • Write ORDER BY whenever order matters. Without it, the database may return rows in any order, and that order can change.

Formatting in the team workflow

Agreeing on a style is easier when nobody has to enforce it by hand. Store queries in .sql files, run a formatter on them before committing, and review the formatted version. For quick checks, such as a query pasted from a log or a colleague's message, an online formatter that runs in the browser is enough, as long as the query does not leave your machine. Queries can contain table names, customer data in literals and internal structure, so avoid tools that upload them.

Common mistakes

  • Reviewing long one-line queries instead of formatting them first.
  • Mixing uppercase and lowercase keywords in the same codebase.
  • Formatting with the wrong dialect and "fixing" errors that are not there.
  • Using SELECT * in application code.
  • Filtering the optional side of a LEFT JOIN in WHERE by accident.
  • Building queries by concatenating user input.

Summary

Readable SQL puts each clause in its own block, each column and condition on its own line and keywords in a consistent case. Formatting never changes what a query does, so apply it freely, but pick the right dialect so the formatter understands your identifiers and parameters. Combined with explicit columns, qualified names and parameters, it turns queries into code that is easy to review.

Keep reading