Messy SQL is a productivity killer. A query that took 20 minutes to write can take 2 hours to debug if it's unformatted. Good SQL formatting isn't about aesthetics — it's about reducing cognitive load when you're six levels deep in a JOIN and trying to figure out where the bug is.
The Core Conventions
Every SQL style guide agrees on the fundamentals. These apply across MySQL, PostgreSQL, SQL Server, BigQuery, and SQLite.
1. Uppercase keywords
``sql
-- Good
SELECT id, name, email
FROM users
WHERE status = 'active'
ORDER BY created_at DESC;
-- Bad
select id, name, email from users where status = 'active' order by created_at desc;
`
Uppercase keywords make the structure of a query immediately scannable. Most formatters apply this automatically.
2. One clause per line
Each major clause — SELECT, FROM, WHERE, JOIN, GROUP BY, ORDER BY, HAVING — should start on its own line. This makes it easy to add, remove, or comment out individual clauses during development.
3. Indent continuation lines
Sub-expressions, JOIN conditions, and WHERE conditions should be indented:
<code>sql
<p>SELECT</p>
<p>u.id,</p>
<p>u.name,</p>
<p>o.total,</p>
<p>p.name AS product_name</p>
<p>FROM users u</p>
<p>INNER JOIN orders o ON o.user_id = u.id</p>
<p>INNER JOIN order_items oi ON oi.order_id = o.id</p>
<p>INNER JOIN products p ON p.id = oi.product_id</p>
<p>WHERE u.status = 'active'</p>
<p>AND o.created_at >= '2024-01-01'</p>
<p>AND o.total > 100</p>
<p>ORDER BY o.created_at DESC;</p>
</code>
4. Leading AND/OR in WHERE clauses
Put AND and OR at the beginning of a condition line, not the end. This makes it trivial to comment out a single condition without breaking the query:
<code>sql
<p>-- Easy to comment out individual conditions:</p>
<p>WHERE status = 'active'</p>
<p>AND created_at >= '2024-01-01'</p>
<p>-- AND deleted_at IS NULL ← just add -- to disable</p>
<p>AND role IN ('admin', 'editor')</p>
</code>
If you put AND at the end of each line, commenting out one line breaks the syntax.
5. Meaningful aliases
Aliases should clarify, not obscure:
`sql
-- Good
FROM users u
INNER JOIN orders o ON o.user_id = u.id
-- Bad
FROM users a
INNER JOIN orders b ON b.a_id = a.id
`
Single-letter aliases are fine when the table name is obvious (u for users), but avoid arbitrary letters that don't relate to the table name.
Naming Conventions
Consistent naming is part of readable SQL. The most widely adopted conventions:
- Tables: snake_case
, plural nouns — users, order_items, product_categories - Columns: snake_case
— user_id, created_at, is_active - Primary keys: id
or {table}_id (e.g. user_id in the users table) - Foreign keys: {referenced_table}_id
(e.g. user_id in orders) - Booleans: is_
or has_ prefix — is_active, has_paid - Timestamps: created_at
, updated_at, deleted_at
Avoid CamelCase — most databases are case-insensitive by default, so UserID and userid are the same identifier unless quoted.
CTEs Over Subqueries
Common Table Expressions (CTEs) make complex queries readable by naming intermediate results:
`sql
-- Hard to read: nested subqueries
SELECT u.name, order_summary.total
FROM users u
INNER JOIN (
SELECT user_id, SUM(total) AS total
FROM orders
WHERE status = 'completed'
GROUP BY user_id
) order_summary ON order_summary.user_id = u.id;
-- Easy to read: CTE
WITH completed_orders AS (
SELECT user_id, SUM(total) AS total
FROM orders
WHERE status = 'completed'
GROUP BY user_id
)
SELECT u.name, co.total
FROM users u
INNER JOIN completed_orders co ON co.user_id = u.id;
`
CTEs also make it easy to debug intermediate steps — you can run the CTE in isolation to verify its output before using it in the main query.
Long WHERE Clauses
When a WHERE clause has many conditions, group related conditions together and add a blank line between groups:
<code>sql
<p>WHERE</p>
<p>-- User filters</p>
<p>u.status = 'active'</p>
<p>AND u.role IN ('admin', 'editor')</p>
<p>-- Date range</p>
<p>AND o.created_at >= '2024-01-01'</p>
<p>AND o.created_at < '2025-01-01'</p>
<p>-- Amount filter</p>
<p>AND o.total BETWEEN 50 AND 500</p>
</code>
Use parentheses whenever you mix AND and OR — operator precedence rules can cause subtle bugs:
`sql
-- BUG: this is parsed as: status = 'active' AND (role = 'admin' OR role = 'editor' AND deleted_at IS NULL)
WHERE status = 'active' AND role = 'admin' OR role = 'editor' AND deleted_at IS NULL
-- CORRECT: explicit grouping
WHERE status = 'active'
AND (role = 'admin' OR role = 'editor')
AND deleted_at IS NULL
`
Automating SQL Formatting
Manual formatting is not sustainable across a team. Automate it:
- VS Code: Install the "SQL Formatter" extension — format on save with Ctrl+Shift+P → Format Document
- Pre-commit hooks: Use sql-formatter
npm package with husky + lint-staged to format .sql files before every commit - Python projects: sqlparse
library — sqlparse.format(sql, reindent=True, keyword_case='upper') - CI/CD: sqlfluff` supports format checking and enforcement in pipelines
For one-off formatting without installing anything, this free SQL formatter runs in your browser, supports 19+ dialects (MySQL, PostgreSQL, BigQuery, Snowflake, T-SQL, SQLite, and more), and never sends your queries to a server.
Summary
Good SQL formatting comes down to a few consistent habits:
- Uppercase keywords, lowercase identifiers
- One clause per line, indented continuation lines
- Leading AND/OR for easy debugging
- Meaningful aliases that relate to table names
- CTEs over nested subqueries for anything complex
- Explicit parentheses when mixing AND/OR
- Automate it — formatters remove the decision overhead entirely
The goal is SQL that a colleague can read and understand at a glance — both when you're writing it and when you're debugging it six months later.
Format SQL instantly in your browser at snappytools.app/sql-formatter-beautifier — supports 19+ dialects, keyword case options, and runs entirely client-side.