SQL Formatting Best Practices: Write Queries Anyone Can Read
A well-formatted SQL query communicates intent as clearly as the data it retrieves. Poorly formatted SQL — with mixed casing, unpredictable line breaks, and unexplained aliases — is one of the most common sources of slow code reviews and subtle production bugs. These conventions make queries easier to read, diff, and maintain.
Keyword casing — UPPERCASE
SQL keywords are case-insensitive, but uppercase keywords are the near-universal convention. They visually separate the structure of a query from its data:
-- Bad
select id, name from users where active = true order by created_at desc;
-- Good
SELECT id, name
FROM users
WHERE active = true
ORDER BY created_at DESC;Identifiers (table names, column names) stay lowercase to match the database schema exactly and avoid quoting issues.
One clause per line
Place each major clause (SELECT, FROM, WHERE, GROUP BY, ORDER BY, LIMIT) on its own line. This makes diffs readable — a diff that changes one WHERE condition should not touch the entire query.
Column lists
For queries with multiple columns, put each column on its own line with a leading comma:
SELECT
u.id
, u.name
, u.email
, o.total AS order_total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.active = true;Leading commas (the river style) make it easy to spot a missing comma — it is always at the beginning of the line, never at the end where it hides. Some teams prefer trailing commas; consistency within a project matters more than which style you choose.
Aliases
- Always use the
ASkeyword for aliases —u.name AS full_nameis clearer thanu.name full_name - Table aliases should be short (1–3 characters) and predictable —
uforusers,ofororders - Avoid single-letter aliases for tables involved in self-joins — use
eandm(employee and manager) instead ofaandb - Column aliases should be snake_case and descriptive —
COUNT(*) AS order_count, notCOUNT(*) AS c
JOIN formatting
SELECT
u.name
, p.title
, c.name AS category_name
FROM users u
INNER JOIN posts p
ON p.author_id = u.id
AND p.published = true
LEFT JOIN categories c
ON c.id = p.category_id
WHERE u.active = true;Indent the ON clause by two more levels than the JOIN. Put multi-condition joins on separate lines with AND aligned. This makes complex joins scannable at a glance.
CTEs (Common Table Expressions)
For queries longer than ~30 lines, use CTEs to name intermediate result sets instead of nesting subqueries:
-- Hard to read — nested subquery
SELECT u.name, order_counts.total
FROM users u
JOIN (
SELECT user_id, COUNT(*) AS total
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
) order_counts ON order_counts.user_id = u.id;
-- Easier to read — CTE
WITH recent_orders AS (
SELECT
user_id
, COUNT(*) AS total
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY user_id
)
SELECT
u.name
, ro.total AS orders_last_30_days
FROM users u
JOIN recent_orders ro ON ro.user_id = u.id;WHERE conditions
- Put each condition on its own line with
AND/ORat the start - Group
ORconditions in parentheses to make precedence explicit:WHERE (status = 'active' OR status = 'trial') AND plan = 'pro' - Prefer
IN (…)over long chains ofOR status = 'a' OR status = 'b' - Avoid functions on indexed columns in WHERE:
WHERE DATE(created_at) = '2026-01-01'prevents index usage; use range comparison instead:WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02'
Avoid SELECT *
SELECT * fetches every column, including ones your application does not use, breaking silently when the schema changes. Always list the specific columns you need. The one exception is exploratory queries in a database client — never in production application code.
Comments in SQL
-- Single-line comment — explain the WHY, not the WHAT
/*
Multi-line comment for longer explanations.
Use when documenting a non-obvious business rule
or a known performance trade-off.
*/
SELECT
user_id
-- Exclude test accounts (emails ending in @example.com)
, COUNT(*) AS real_user_events
FROM events
WHERE email NOT LIKE '%@example.com'
GROUP BY user_id;Try it yourself
Use the free browser-based SQL Formatter on DevBench — no signup, runs entirely in your browser.
Open SQL Formatter