Writing a SQL query that returns correct results is only half the job — the other half is writing it so a teammate (or you, six months later) can actually follow the logic. SQL doesn't care about whitespace or capitalization, which makes it easy to write technically-correct queries that are practically unreadable.
Core formatting principles
1. Capitalization
The widely accepted convention: uppercase SQL keywords (SELECT, FROM, WHERE, JOIN, GROUP BY), lowercase identifiers (table/column names, ideally snake_case). This visual contrast makes it instantly clear what's native SQL syntax versus your specific data model.
-- Bad
select customer_name, count(order_id) from sales.orders group by customer_name;
-- Good
SELECT
customer_name,
COUNT(order_id) AS total_orders
FROM sales.orders
GROUP BY
customer_name;
2. Indentation and line breaks
- One column per line in the
SELECTclause — makes it trivial to comment out a single column while debugging, and keeps git diffs clean - Indent sub-clauses consistently (2 or 4 spaces; avoid tabs, which render inconsistently across editors)
- Trailing commas are the practical standard — leading commas have their advocates, but pick one and keep the team consistent
3. Meaningful aliasing
Use AS explicitly even though SQL allows omitting it — it makes intent unambiguous. Avoid single-letter table aliases in anything non-trivial; emp and cust read far better than a and b three joins deep.
-- Bad
SELECT a.name, b.amount
FROM customers a
JOIN orders b ON a.id = b.customer_id;
-- Good
SELECT
c.name AS customer_name,
o.amount AS order_amount
FROM customers AS c
JOIN orders AS o
ON c.id = o.customer_id;
Structuring complex queries
4. CTEs over nested subqueries
Nested subqueries force the reader to parse from the inside out. Common Table Expressions (WITH clause) let you define result sets sequentially, so the query reads top-to-bottom the way you'd actually explain the logic out loud.
WITH active_users AS (
SELECT user_id, email
FROM users
WHERE status = 'active'
),
recent_orders AS (
SELECT user_id, SUM(total_amount) AS total_spent
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
)
SELECT
u.email,
o.total_spent
FROM active_users AS u
JOIN recent_orders AS o
ON u.user_id = o.user_id;
Name CTEs descriptively — cte1 is exactly as unhelpful as an alias named a.
5. JOIN formatting
- Put
ONon its own line, indented under theJOIN - Multiple join conditions: each
ANDon its own line, aligned underON - Always write the explicit join type (
INNER JOIN,LEFT JOIN) rather than bareJOIN
SELECT
e.employee_name,
d.department_name
FROM employees AS e
LEFT JOIN departments AS d
ON e.department_id = d.department_id
AND d.is_active = true;
Filtering and grouping
6. Clean WHERE clauses
One condition per line, and parenthesize explicitly whenever AND and OR are mixed — never rely on a reader remembering operator precedence.
SELECT
product_id,
product_name
FROM inventory
WHERE stock_level < 10
AND (category = 'Electronics' OR category = 'Accessories')
AND is_discontinued = false;
7. Explicit GROUP BY / ORDER BY
Grouping or ordering by column index (GROUP BY 1, 2) saves keystrokes but is a real, silent-failure risk: if someone later inserts a new column at the front of the SELECT list, the index-based GROUP BY now points at the wrong column with no error thrown. Writing out explicit column names or aliases avoids this entirely.
Automating it
Manually enforcing formatting rules across a team is tedious and creates unproductive code review debates. SQLFluff (linter, works across dialects) or the formatter built into dbt can check and auto-fix formatting against a defined style guide — wiring one into CI means style is settled before code review even starts, so reviews focus on query logic instead of indentation. For a quick one-off format without setting up tooling, ToolSink's SQL Formatter applies consistent casing and indentation directly in the browser.
Conclusion
Consistent capitalization, deliberate whitespace, CTEs over nested subqueries, and explicit joins/grouping turn SQL from a write-once script into something a team can actually maintain. Agree on a style guide, automate enforcement with a linter, and the debate over formatting disappears from code review entirely.