How to Format Messy SQL Queries for Readability
Turn messy one-line SQL into readable code with rules for uppercase keywords, consistent indentation, CTE layout, subquery and JOIN formatting, with examples.
Why SQL Formatting Matters
SQL is the only language where a 200-line query written as a single line is common in production. Developers copy queries from application logs, ORM query inspectors, or database consoles and get a wall of text:
SELECT u.id,u.name,u.email,o.id,o.total,o.created_at FROM users u LEFT JOIN orders o ON u.id=o.user_id WHERE o.created_at > '2026-01-01' AND o.status = 'completed' AND u.active = true ORDER BY o.created_at DESC LIMIT 50
This is valid SQL. It is also unreadable. Formatting transforms it into something a human can reason about. When a query breaks, the first step is always to format it. You can't debug what you can't read.
The Core Rules
Uppercase Keywords
SQL keywords (SELECT, FROM, WHERE, JOIN, AND, ORDER BY) should be
uppercase. This separates the structure of the query from the data (table names,
column names, values):
SELECT u.id, u.name, u.email
FROM users u
WHERE u.active = true;
Lowercase keywords are valid but harder to scan. Uppercase is a convention shared by nearly every SQL style guide. Your eyes learn to skip over the uppercase words and focus on the changing parts: column names, conditions, and values.
One Clause Per Line
Every major clause starts on a new line, left-aligned:
SELECT ...
FROM ...
JOIN ...
ON ...
WHERE ...
AND ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...
The query reads top-to-bottom as a logical flow: select these columns, from these tables, joined on this condition, filtered by these criteria, grouped, ordered, limited.
When you come back to a query six months later, you should be able to scan the
left margin and understand its structure instantly. No hunting for where the
WHERE ends and the ORDER BY begins.
Indentation for Sub-Elements
Columns in SELECT are indented. Join conditions are indented under JOIN.
AND clauses are indented under WHERE:
SELECT
u.id,
u.name,
u.email,
o.total
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
WHERE o.created_at > '2026-01-01'
AND o.status = 'completed'
AND u.active = true
ORDER BY o.created_at DESC
LIMIT 50;
A 2-space or 4-space indent works. Pick one and apply it everywhere. Inconsistency is worse than the wrong choice.
One Column Per Line for SELECT with 3+ Columns
-- Good for 1-2 columns
SELECT id, name FROM users;
-- Good for 3+ columns
SELECT
id,
name,
email,
created_at,
last_login
FROM users;
When a SELECT has 8 columns with aggregate functions and aliases, putting them
all on one line makes it impossible to spot that SUM(o.total) is missing. One
column per line makes every column visible and every omission obvious.
Comma Placement
There are two schools. Leading commas:
SELECT
id
, name
, email
FROM users;
Trailing commas:
SELECT
id,
name,
email
FROM users;
Leading commas make it easier to add and remove lines without trailing-comma errors. Trailing commas are more common and look more like other languages. Pick one and apply it consistently. Mixing them is the only bad choice.
Common Table Expressions (CTEs)
CTEs benefit from careful formatting. Each CTE gets its own indented block:
WITH recent_users AS (
SELECT id, name
FROM users
WHERE created_at > '2026-01-01'
),
user_orders AS (
SELECT
u.name,
COUNT(o.id) AS order_count
FROM recent_users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.name
)
SELECT *
FROM user_orders
WHERE order_count > 0
ORDER BY order_count DESC;
Place each CTE name on its own line. Open the parentheses on the same line as
the CTE name. Indent the content by 2 spaces. Close the parentheses at the
same indent level as the CTE name, followed by a comma for all CTEs except
the last. The final SELECT is at the top level, not indented.
For long CTE chains (5+ CTEs), add a comment above each CTE explaining what it produces. Future you will thank past you.
Subqueries
Subqueries should be indented and clearly delimited with parentheses on their own lines:
SELECT name
FROM users
WHERE id IN (
SELECT user_id
FROM orders
WHERE total > 100
);
A subquery on one line is fine when it fits. A 15-line subquery buried inside
a WHERE clause should be extracted as a CTE. CTEs give the subquery a name
and make the outer query read like plain English.
Correlated Subqueries
When a subquery references a column from the outer query, indent it clearly:
SELECT
u.name,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;
Correlated subqueries can be slow on large tables. The inner query runs once
per outer row. Consider rewriting as a LEFT JOIN with GROUP BY if the
dataset is large.
JOIN Formatting
Always use explicit JOIN syntax, never comma-separated tables in FROM:
-- Bad: implicit join
SELECT u.name, o.total
FROM users u, orders o
WHERE u.id = o.user_id;
-- Good: explicit join
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id;
The ON clause is indented under the JOIN. For multiple joins, align the
JOIN keywords at the same level:
SELECT 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;
For complex ON conditions with multiple predicates:
JOIN orders o
ON u.id = o.user_id
AND o.status = 'completed'
AND o.created_at > '2026-01-01'
CASE Expressions
SELECT
name,
CASE
WHEN total > 1000 THEN 'premium'
WHEN total > 500 THEN 'standard'
ELSE 'basic'
END AS tier
FROM users;
CASE and END at the same level. Each WHEN/ELSE indented. The column
alias follows END.
Window Functions
SELECT
name,
department,
salary,
RANK() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS dept_rank
FROM employees;
The OVER clause opens on the same line. PARTITION BY and ORDER BY inside
OVER are indented.
Use the Formatter
The SQL Formatter applies these rules automatically. Paste any messy query and get consistently formatted output in your preferred SQL dialect. It handles:
- Uppercase keywords
- Consistent indentation (2 or 4 spaces)
- Column-per-line for multi-column SELECTs
- BETWEEN, IN, and CASE expression formatting
- Dialect-specific quoting (backticks, double quotes, brackets)
Try it yourself: open the SQL Formatter. Paste
select id,name,email from users where active=true order by nameand click Format. The output is structured, indented, and readable. Try a complex query with JOINs, subqueries, and a CTE to see how nesting is handled. Switch between 2-space and 4-space indentation.