SQL Formatting Guide: Writing Clean and Readable Queries

May 28, 20266 min read

Why SQL Formatting Matters

Poorly formatted SQL is hard to read, harder to debug, and nearly impossible for teammates to review. A single-line query with five JOINs stuffed together wastes time every time someone opens it.

Consistent formatting solves this. When every query follows the same structure, your team can scan, understand, and modify code faster. Formatting also catches mistakes — misaligned clauses and missing conditions stand out when your SQL follows a predictable pattern.

Core Formatting Principles

Three rules form the foundation of readable SQL.

Capitalize Keywords

Write SQL keywords in uppercase. This separates language structure from your data immediately.

-- Hard to scan
select name, email from users where active = true order by name;

-- Easy to scan
SELECT name, email
FROM users
WHERE active = true
ORDER BY name;

Your eye jumps straight to the keywords. This matters most in long queries where SELECT, FROM, and WHERE might be hundreds of lines apart.

Indent and Align

Place each major clause on its own line. Indent subordinate content two or four spaces beneath its parent clause.

SELECT
    u.id,
    u.name,
    u.email,
    d.department_name
FROM users u
JOIN departments d
    ON u.department_id = d.id
WHERE u.active = true
    AND u.hire_date > '2024-01-01'
ORDER BY u.name;

The ON condition sits beneath its JOIN. The AND aligns under WHERE. This visual hierarchy maps directly to the query's logic.

Use Line Breaks for Long Lists

When a SELECT or IN clause contains many items, stack them vertically.

SELECT
    product_id,
    product_name,
    category,
    price,
    stock_quantity,
    reorder_level
FROM products
WHERE category IN (
    'Electronics',
    'Office Supplies',
    'Software'
);

Vertical lists make it easy to spot missing items or spot duplicates. They also produce cleaner diffs in version control — adding one column changes one line, not a wrapped paragraph.

Formatting Each Clause

SELECT

Place each column on its own line when the list exceeds three items. Put a trailing comma after every column except the last.

SELECT
    order_id,
    customer_id,
    order_date,
    total_amount,
    status,

This trailing comma style prevents errors when you add or remove columns — no need to fix the last line.

FROM and JOIN

Start each JOIN on a new line. Keep the join type, table, and condition visible at a glance.

FROM orders o
JOIN customers c
    ON o.customer_id = c.id
LEFT JOIN shipping s
    ON o.shipping_id = s.id
LEFT JOIN invoices i
    ON o.invoice_id = i.id

For multi-table queries, list tables in logical order — starting with the primary table, then joining dependent tables outward.

WHERE

Align conditions under WHERE. Group related conditions together.

WHERE o.status = 'shipped'
    AND o.order_date >= '2024-01-01'
    AND (
        c.country = 'US'
        OR c.country = 'CA'
    )

Put the most selective condition first. This helps human readers quickly understand the query's scope, and some database optimizers evaluate conditions in written order for early filtering.

GROUP BY and ORDER BY

Mirror the indentation style of SELECT. Align aggregate functions and sort directions.

GROUP BY
    c.country,
    c.region

ORDER BY
    total_revenue DESC,
    c.country ASC

Before and After Comparison

Here is a real query before formatting:

select u.name,sum(p.amount) as total from users u join payments p on u.id=p.user_id where p.status='completed' and p.payment_date>='2024-01-01' group by u.name having sum(p.amount)>1000 order by total desc limit 10;

And after:

SELECT
    u.name,
    SUM(p.amount) AS total
FROM users u
JOIN payments p
    ON u.id = p.user_id
WHERE p.status = 'completed'
    AND p.payment_date >= '2024-01-01'
GROUP BY
    u.name
HAVING SUM(p.amount) > 1000
ORDER BY total DESC
LIMIT 10;

The formatted version takes more vertical space but communicates its logic in seconds rather than minutes.

Subquery Formatting

Indent subqueries one level deeper than their parent clause. Give them a clear alias.

SELECT
    department,
    avg_salary
FROM (
    SELECT
        d.name AS department,
        AVG(e.salary) AS avg_salary
    FROM employees e
    JOIN departments d
        ON e.department_id = d.id
    GROUP BY d.name
) AS dept_stats
WHERE avg_salary > 75000
ORDER BY avg_salary DESC;

For CTEs (Common Table Expressions), apply the same rules — readable SQL inside the CTE, readable SQL in the main query.

WITH monthly_revenue AS (
    SELECT
        DATE_TRUNC('month', order_date) AS month,
        SUM(amount) AS revenue
    FROM orders
    WHERE status = 'completed'
    GROUP BY DATE_TRUNC('month', order_date)
)
SELECT
    month,
    revenue,
    LAG(revenue) OVER (ORDER BY month) AS prev_month
FROM monthly_revenue
ORDER BY month;

Key Takeaways

  • Capitalize all SQL keywords to separate structure from data
  • Place clauses on separate lines and indent subordinate content
  • Stack long column lists vertically for scannability and clean diffs
  • Format subqueries and CTEs with the same rules as the main query
  • Consistent formatting catches bugs faster than any linter

Try It Yourself

Format your queries instantly with our free SQL Formatter. Paste any SQL statement and get clean, consistently formatted output in one click.