The Developer's Guide to SQL Query Formatting and Readability
Why SQL Formatting Matters
SQL (Structured Query Language) is declarative: you describe what data you want, and the database query planner determines how to retrieve it. However, as business logic grows, SQL queries inevitably accumulate nested subqueries, CTEs (Common Table Expressions), multiple JOIN conditions, window functions, and complex CASE statements.
Reading an unformatted, 100-line single-string SQL query during a production outage is an exercise in frustration. Proper query formatting reduces bugs, speeds up pull request reviews, and makes query optimization visible.
Standard SQL Formatting Conventions
1. Capitalize All SQL Reserved Keywords
SQL engines are case-insensitive regarding keywords, but capitalizing keywords provides visual hierarchy, separating language grammar from database schema entities (tables and columns):
-- Poor: Hard to separate keywords from identifiers
select u.id, u.email, o.total_amount from users u left join orders o on u.id = o.user_id where o.status = 'completed';
-- Excellent: Clear separation of structure and data
SELECT
u.id,
u.email,
o.total_amount
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
WHERE o.status = 'completed';
2. Indent JOIN Conditions
Always place the ON constraint on an indented line directly beneath the JOIN clause. This immediately communicates how relationships are bounded and makes it obvious if an outer join is missing join keys:
SELECT
orders.id,
customers.company_name,
payments.amount
FROM orders
INNER JOIN customers
ON orders.customer_id = customers.id
LEFT JOIN payments
ON payments.order_id = orders.id
AND payments.status = 'captured';
3. Format Common Table Expressions (CTEs) Clearly
CTEs (WITH clauses) make long queries modular and readable. Format each CTE with clear indentation and trailing commas:
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', created_at) AS revenue_month,
SUM(amount) AS total_revenue
FROM invoices
WHERE status = 'paid'
GROUP BY 1
),
churned_customers AS (
SELECT
customer_id,
cancelled_at
FROM subscriptions
WHERE cancelled_at IS NOT NULL
)
SELECT
r.revenue_month,
r.total_revenue,
COUNT(c.customer_id) AS churn_count
FROM monthly_revenue r
LEFT JOIN churned_customers c
ON DATE_TRUNC('month', c.cancelled_at) = r.revenue_month
GROUP BY r.revenue_month, r.total_revenue
ORDER BY r.revenue_month DESC;
SQL Performance Anti-Patterns to Spot During Formatting
When you format messy SQL into structured blocks, common performance traps become immediately visible:
SELECT *in Production: Always specify explicit column names. Querying unusedtextorjsonbcolumns increases network serialization and prevents index-only scans.- Leading Wildcard
LIKE '%term': Bypasses standard B-Tree indexes, triggering costly sequential table scans. Use Postgres GIN indexes or trigram matching instead. - Functions on Indexed Columns: Writing
WHERE DATE(created_at) = '2026-08-01'prevents the query planner from using the index oncreated_at. WriteWHERE created_at >= '2026-08-01' AND created_at < '2026-08-02'instead.
Automating SQL Cleanup in Your Browser
Manually aligning commas and indenting JOIN clauses across legacy queries is tedious. With our free in-browser SQL Query Formatter, you can paste messy, minified SQL strings and transform them into beautifully indented, syntax-highlighted SQL ready for documentation or migration scripts.
If your queries involve JSON data transformations (such as PostgreSQL jsonb columns), you can also validate structured payloads using our JSON to XML Converter or JSON Validator & Formatter. Pair your database migrations with unique primary keys from the UUID Generator Suite.