Why SQL Formatting Matters
Unformatted SQL is one of the hardest things to review in a pull request. A 200-character single-line query that joins three tables, filters on five conditions, and groups by two columns is technically valid, but it forces every reader to mentally parse the clauses before they can reason about correctness. Consider this real-world example:
select u.id,u.name,o.total,p.name from users u join orders o on u.id=o.user_id join products p on o.product_id=p.id where o.status='completed' and u.active=1 and o.created_at>'2025-01-01' order by o.total desc limit 50;
This query works, but good luck spotting a missing join condition or an incorrect filter during code review. Consistent SQL formatting eliminates this friction. It makes queries scannable, diffable, and far easier to maintain over time. The conventions in this guide are followed by most SQL style guides and supported by every major SQL formatter.
Uppercase Keywords
The most widely adopted SQL convention is to write keywords in uppercase and keep table names, column names, and aliases in lowercase (or whatever case your schema uses). This creates an immediate visual separation between the structure of a query and the data it operates on.
SELECT u.id, u.name, u.email FROM users u WHERE u.active = 1 ORDER BY u.name ASC;
At a glance, you can pick out SELECT, FROM, WHERE, and ORDER BY without reading the column names. The uppercase keywords act like section headers, guiding your eye through the query's logical flow.
This applies to all SQL keywords: SELECT, FROM, WHERE, JOIN, ON, AND, OR, INSERT INTO, UPDATE, SET, DELETE, GROUP BY, ORDER BY, HAVING, LIMIT, AS, IN, NOT, NULL, BETWEEN, EXISTS, CASE, WHEN, THEN, ELSE, END, and others.
SQL itself is case-insensitive for keywords, so this is purely a readability convention. However, it is so universally expected that lowercase keywords in a shared codebase will look wrong to most database developers.
One Clause Per Line
Each major SQL clause should start on its own line. This is the single most impactful formatting rule because it turns a wall of text into a structured, readable document. Here is a poorly formatted query followed by its properly formatted version:
Before:
SELECT u.id, u.name, u.email, o.id AS order_id, o.total, p.name AS product_name FROM users u JOIN orders o ON u.id = o.user_id JOIN products p ON o.product_id = p.id WHERE u.active = 1 AND o.status = 'completed' ORDER BY o.total DESC LIMIT 100;
After:
SELECT
u.id,
u.name,
u.email,
o.id AS order_id,
o.total,
p.name AS product_name
FROM users u
JOIN orders o
ON u.id = o.user_id
JOIN products p
ON o.product_id = p.id
WHERE u.active = 1
AND o.status = 'completed'
ORDER BY o.total DESC
LIMIT 100;
The formatted version lets you immediately see three things: what columns are selected, which tables are joined and how, and what filters apply. Each clause starts at the left margin, and continuation lines (like ON conditions) are indented one level. When this query appears in a diff, a change to any single clause shows up as a one-line change instead of a modification to an unreadable 300-character line.
Indenting Subqueries
When a query contains a subquery, indent the inner SELECT one level (typically four spaces) to show the nesting visually. This mirrors the way developers indent nested blocks in application code and makes it clear where the subquery begins and ends.
SELECT
u.id,
u.name,
u.lifetime_total
FROM users u
WHERE u.lifetime_total > (
SELECT AVG(lifetime_total)
FROM users
WHERE active = 1
)
ORDER BY u.lifetime_total DESC;
For correlated subqueries in the SELECT list, indent the subquery body and keep the closing parenthesis aligned with the opening line:
SELECT
d.name AS department,
(
SELECT COUNT(*)
FROM employees e
WHERE e.department_id = d.id
) AS employee_count
FROM departments d
ORDER BY employee_count DESC;
If you find yourself nesting more than two levels deep, consider refactoring into Common Table Expressions (CTEs) using WITH. CTEs flatten the nesting and let you give meaningful names to intermediate result sets.
Column Lists
When a SELECT statement returns more than two or three columns, place each column on its own line. This makes it trivial to add, remove, or reorder columns and produces clean diffs in version control.
There are two popular comma styles: trailing commas and leading commas.
Trailing commas (more common in application code):
SELECT
u.id,
u.first_name,
u.last_name,
u.email,
u.created_at
FROM users u;
Leading commas (popular among SQL developers):
SELECT
u.id
,u.first_name
,u.last_name
,u.email
,u.created_at
FROM users u;
The leading-comma style has a practical advantage: you can comment out any line except the first without creating a syntax error, and adding a new column at the end never requires editing the previous line. However, trailing commas are more familiar to most programmers and are the default output of nearly every SQL formatter. Pick one style and use it consistently across your team.
WHERE Clause Formatting
When a WHERE clause contains multiple conditions, place each AND or OR on a new line, indented one level beneath WHERE. This makes it easy to scan every filter condition and spot logical errors.
SELECT o.id, o.total, o.status
FROM orders o
WHERE o.status = 'completed'
AND o.total > 100.00
AND o.created_at >= '2025-01-01'
AND o.created_at < '2026-01-01';
When you mix AND and OR, use parentheses to make the logic explicit and indent the grouped conditions together:
SELECT u.id, u.name
FROM users u
WHERE u.active = 1
AND (
u.role = 'admin'
OR u.role = 'moderator'
)
AND u.last_login >= '2025-06-01';
Without the parentheses and clear formatting, it is dangerously easy to misread the precedence of AND and OR. Making the grouping visible prevents subtle bugs that can silently return incorrect result sets.
Comments in SQL
SQL supports two comment styles, and both are valuable for different purposes.
Inline comments use two dashes (--) and extend to the end of the line. Use them to annotate individual clauses or explain non-obvious filter logic:
SELECT
u.id,
u.name,
u.email
FROM users u
WHERE u.active = 1 -- exclude deactivated accounts
AND u.created_at >= '2025-01-01' -- new users only
ORDER BY u.created_at DESC;
Block comments use /* */ and can span multiple lines. They are ideal for documenting the purpose of a complex query at the top, or for temporarily disabling a section during debugging:
/*
Monthly revenue report
Returns total revenue per product category
for the current calendar year.
*/
SELECT
c.name AS category,
SUM(o.total) AS revenue
FROM orders o
JOIN products p ON o.product_id = p.id
JOIN categories c ON p.category_id = c.id
WHERE o.status = 'completed'
AND o.created_at >= '2026-01-01'
GROUP BY c.name
ORDER BY revenue DESC;
A few guidelines for SQL comments: keep them concise, explain why rather than what, and avoid commenting out large blocks of dead code in production queries. If a query variant is no longer needed, delete it rather than leaving it commented out.
Minifying SQL for Production
While formatted SQL is essential for development, there are legitimate cases where single-line (minified) SQL is preferable:
- Application code strings: When embedding SQL in a language that does not support multi-line strings elegantly, a single-line query avoids awkward string concatenation. Many ORMs and query builders output single-line SQL by default.
- Log files: Compact single-line queries are easier to grep in log files since each query occupies exactly one line. This simplifies log parsing and monitoring.
- Network transfer: Stripping whitespace and comments from SQL before sending it over the network reduces payload size. This matters most in high-throughput systems executing thousands of queries per second.
- Prepared statement caching: Some database drivers normalize whitespace when computing statement cache keys, but others do not. Using consistent minified SQL can improve cache hit rates in the latter case.
The key principle is that source-of-truth SQL should always be formatted. Minification is a deployment or runtime concern, not a development concern. Store your queries in formatted form in version control, and let your tooling or application layer handle minification when needed.
-- Formatted (stored in source control) SELECT u.id, u.name FROM users u WHERE u.active = 1 ORDER BY u.name; -- Minified (generated at runtime) SELECT u.id,u.name FROM users u WHERE u.active=1 ORDER BY u.name;