Skip to content

← All writing

How to Format SQL: A Pragmatic Style Guide

· by Andergrove Software

SQL is one of the few languages where a 40-line statement routinely arrives as a single line — copied out of a log, an ORM’s debug output, or a dashboard definition — and where teams still argue about uppercase keywords in code review. The arguments persist because SQL has no official style guide and no dominant auto-formatter the way Go or Python do. But a handful of conventions have clearly won in practice, and they all serve one goal: making the shape of the query — what it selects, what it joins, what it filters — visible at a glance. Here they are, with the reasoning, so your team can pick once and stop debating.

Uppercase keywords: the convention that won

SQL keywords are case-insensitive, so select, SELECT and Select all run. The dominant convention is uppercase keywords, lowercase identifiers:

SELECT id, email, created_at
FROM users
WHERE status = 'active';

The rationale is syntax highlighting for environments that don’t have any — a psql session, a log file, a Slack message. Casing splits the statement into two visual layers: the fixed scaffolding of the language (uppercase) and your schema’s names (lowercase). A minority convention — lowercase everything, let the editor highlight — is gaining ground in analytics teams, and it is fine if your SQL only ever lives where highlighting exists. Uppercase travels better, which is why it remains the default in every widely used style guide.

For identifiers themselves, use snake_case and skip quoted identifiers wherever possible — the moment a column is named "Order Date", every query that touches it needs the quotes, forever.

One clause per line, one column per line (past trivial)

The single highest-value rule: each major clause starts its own lineSELECT, FROM, each JOIN, WHERE, GROUP BY, ORDER BY. A reader scanning the left margin sees the query’s skeleton without reading a word of the middle.

For the select list, put each column on its own line once you pass two or three. The payoff is not aesthetics — it is diffs. When each column occupies a line, adding one produces a one-line diff and a merge conflict only when two people touch the same column. A wrapped, comma-run select list turns every change into a reflowed paragraph that reviewers skim instead of reading.

SELECT
    u.id,
    u.email,
    o.total_amount,
    o.placed_at
FROM users AS u
JOIN orders AS o ON o.user_id = u.id
WHERE o.placed_at >= '2026-01-01'
ORDER BY o.placed_at DESC;

Always AS for aliases, and resist one-letter aliases outside short examples like this one — orders AS o is fine in a ten-line query, hostile in a two-hundred-line one.

The leading-comma schism

The one genuinely contested question. Trailing commas are ordinary punctuation:

SELECT
    id,
    email,
    created_at

Leading commas put the comma at the start of the next line:

SELECT
    id
    , email
    , created_at

Leading-comma advocates have a real argument: you can comment out any column except the first without breaking the query, and a missing comma is visually obvious because the column alignment breaks. This matters in dialects and tools where debugging means commenting lines in and out all day, which is why the style is common among data analysts and rare among application developers.

Both work. The honest advice: this is the least important rule in this guide — pick whichever your team’s most-edited codebase already uses and enforce it with a formatter, because a mixed file is worse than either convention. (The underlying annoyance is SQL’s refusal to allow a trailing comma after the last column — a forty-year-old grammar decision that also bites in JSON, where trailing commas are the classic parse error in the other direction.)

JOINs, subqueries, and CTEs

Put each JOIN at the left margin with its ON condition on the same line (or indented directly below when the condition is compound). Spelling out INNER JOIN vs. plain JOIN is taste; spelling out LEFT JOIN is not optional, and always write the join condition next to the join it belongs to — a WHERE clause that quietly turns a left join into an inner join (WHERE right_table.col = ... filters out the NULLs) is one of SQL’s classic silent bugs, and keeping conditions with their joins is how you spot it.

When a query nests more than one subquery deep, convert to CTEs. A WITH clause turns inside-out reading (innermost subquery first) into top-to-bottom reading, and each step gets a name that documents intent:

WITH active_users AS (
    SELECT id, email
    FROM users
    WHERE status = 'active'
),
recent_orders AS (
    SELECT user_id, SUM(total_amount) AS total_spent
    FROM orders
    WHERE placed_at >= '2026-01-01'
    GROUP BY user_id
)
SELECT au.email, ro.total_spent
FROM active_users AS au
JOIN recent_orders AS ro ON ro.user_id = au.id;

In modern engines the optimizer inlines simple CTEs, so the readability is close to free — measure before assuming otherwise.

Stop formatting by hand

None of these rules are worth enforcing manually. Adopt a formatter, put the config in the repo, run it in CI, and query style stops being a code-review topic at all — the same trajectory every other language has followed. For dbt and analytics codebases, sqlfluff lints and fixes against a configurable dialect; application teams usually reach for their IDE’s built-in SQL formatting or an editor plugin.

And for the daily case that starts this whole story — a one-line monster pasted out of a log — our SQL formatter applies these conventions in one click: clause-per-line layout, keyword casing, one column per line, and a minify mode for going the other way. It runs entirely in your browser, so the query and whatever data it embeds never leave your machine — and the companion guide covers the options. Format the query first; then debug it. Structure you can see is half the diagnosis.