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:
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 10It works, but reading it means scanning back and forth to find where the join condition ends and the filter begins. 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 o.created_at >= '2026-01-01'
GROUP BY
u.id,
u.name
HAVING
count(o.id) > 5
ORDER BY
orders DESC
LIMIT
10Now 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:
- One clause per block.
SELECT,FROM,WHERE,GROUP BY,HAVING,ORDER BYandLIMITstart at the left margin, and their content is indented below. - One column per line in
SELECT. Adding, removing or commenting out a column becomes a one-line change, and diffs stay small. - One condition per line, with the operator first. Starting lines with
ANDandORmakes the logic visible and easy to comment out. - Joins with their condition on the same line.
LEFT JOIN orders o ON o.user_id = u.idkeeps each relationship readable as a unit. - 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. - Meaningful aliases.
uforusersandoforordersare fine in a short query. In a long one,customers candinvoices ibeatt1andt2.
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:
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 DESCEach 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,$2for 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_atcannot become ambiguous when someone adds acreated_atto another table. - Put join conditions in
ONand filters inWHERE, and think twice when aWHEREcondition references the optional side of aLEFT 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 BYwhenever 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 JOINinWHEREby 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
Base64 is not encryption — what it is, when to use it and the URL-safe variant
How Base64 turns bytes into text, why it hides nothing, how to handle accents and emoji correctly and when you need the URL-safe alphabet.
November 5, 20265 min readTool: Base64 Encoder/Decoder
The regex that freezes the browser — catastrophic backtracking explained
Why a short regular expression can take seconds or hang a page, how to recognize nested quantifiers and how to rewrite the pattern so it stays fast.
November 5, 20266 min readTool: Regex Tester