How to Format SQL Queries
4 min read · SQL
Why formatting matters
SQL is written once and read many times — by you, by teammates, and by your future self debugging a production issue at 2 AM. Unformatted SQL is hard to scan, hard to diff, and easy to misread.
Compare these two queries that do the same thing:
-- Unformatted select u.id,u.name,o.total from users u join orders o on u.id=o.user_id where o.total>100 and u.active=true order by o.total desc limit 10; -- Formatted SELECT u.id, u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE o.total > 100 AND u.active = true ORDER BY o.total DESC LIMIT 10;
The core rules
1. Uppercase SQL keywords
Write SELECT, FROM, WHERE, JOIN, AND, OR in uppercase. Lowercase for table names, column names, aliases. This visually separates the query structure from your data model.
2. One major clause per line
Each top-level keyword (SELECT, FROM, WHERE, GROUP BY, etc.) starts on its own line, left-aligned.
3. Indent column lists
Columns in the SELECT clause are indented two spaces and listed one per line for three or more columns.
4. Align ON conditions with JOIN
The ON clause of a JOIN goes on the same line, or indented below it for long conditions.
5. Indent AND / OR conditions
Each condition in a WHERE clause goes on its own line, with AND/OR at the start of the line (not the end).
Formatting JOINs
SELECT u.id, u.name, p.plan_name, COUNT(o.id) AS order_count FROM users u JOIN subscriptions s ON u.id = s.user_id JOIN plans p ON s.plan_id = p.id LEFT JOIN orders o ON u.id = o.user_id WHERE u.active = true AND s.expires_at > NOW() GROUP BY u.id, u.name, p.plan_name HAVING COUNT(o.id) > 0 ORDER BY order_count DESC;
Note: each JOIN is at the same indentation level as FROM. The ON condition follows on the same line.
Formatting subqueries
Indent subqueries by two spaces and always give them a meaningful alias:
SELECT
u.name,
recent.total_spent
FROM users u
JOIN (
SELECT
user_id,
SUM(amount) AS total_spent
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
) recent ON u.id = recent.user_id
ORDER BY recent.total_spent DESC;CTEs (WITH clauses)
Common Table Expressions are a cleaner alternative to nested subqueries. They make complex queries much easier to read:
WITH recent_orders AS (
SELECT
user_id,
SUM(amount) AS total_spent
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
),
active_users AS (
SELECT id, name
FROM users
WHERE active = true
)
SELECT
au.name,
ro.total_spent
FROM active_users au
JOIN recent_orders ro ON au.id = ro.user_id
ORDER BY ro.total_spent DESC;Naming conventions in SQL
- Table names: plural,
snake_case(users,order_items) - Column names:
snake_case(created_at,user_id) - Aliases: short but meaningful —
uforusers,ofororders - CTE names: descriptive —
recent_orders,active_users - Avoid
SELECT *in production queries — always name the columns you need
Format SQL instantly
Paste any SQL query and auto-format it with proper indentation and keyword casing.
SQL Formatter →