Back to blogGuides

SQL Formatting Conventions That Make Code Review Easier

Jul 21, 2026 · 5 min read

SQL doesn't get the same formatting discipline as application code. There's no equivalent of Prettier running on every commit for most teams, so queries drift toward whatever shape the last person who touched them left it in — inconsistent keyword casing, inconsistent indentation, joins that wrap differently from file to file, sometimes within the same migration. Nobody decided this was fine; it's just what happens when formatting is nobody's job.

The convention worth adopting first is picking one keyword case for the whole codebase and sticking to it. It doesn't matter much whether it's uppercase `SELECT` or lowercase `select` — what matters is that a reviewer's eye doesn't have to re-parse casing on every query to find the actual logic. Mixed casing within a single file is the worst version of this: it reads as if two people wrote the query without talking to each other, because usually they did.

The second is consistent indentation on joins and conditions, so a query with four joins reads as four aligned lines instead of a wall of text. Put each join on its own line, indent its `ON` clause underneath it, and keep the join type (`LEFT JOIN`, `INNER JOIN`) visually aligned down the left edge. This is the single biggest readability lever for anything beyond a trivial query — a five-join query formatted this way is scannable in seconds; the same query on one wrapped line is not.

The same logic applies inside a `WHERE` clause once it has more than one or two conditions. Stack conditions one per line with the boolean operator leading the next line, not trailing the previous one — `AND status = 'active'` reads better starting a new line than hanging off the end of the line above it, because a reviewer scanning down the left edge sees every condition at a glance instead of hunting for `AND`s buried mid-line.

Subqueries and CTEs deserve their own convention: indent a subquery's body one level deeper than the line that opens it, and for a query with more than one CTE, give each one a full blank line of separation with a comment naming what it's for. A query with three unlabeled CTEs stacked on top of each other reads like a maze; the same query with `-- active users in the last 30 days` above each one tells a reviewer what to expect before they read the SQL itself.

Column lists are the other place formatting either helps or actively hides bugs. Aligning column names one-per-line in a wide `SELECT` makes it trivial to spot a duplicate column, a missing alias, or a column pulled from the wrong table — all things that are nearly invisible in a single dense line. It's also where `SELECT *` causes the most damage in review: a diff on a wildcard select can't show a reviewer which columns actually changed, because none are named.

Window functions are worth a convention of their own, because a poorly formatted `OVER` clause is one of the least readable constructs in SQL. Break `PARTITION BY` and `ORDER BY` onto their own indented lines inside the `OVER (...)` block rather than cramming them onto one line with the function call — `ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)` reads fine short, but the moment there are two partition columns and a frame clause, a single line stops being reviewable and needs the same one-clause-per-line treatment as a join.

`UNION` and `UNION ALL` queries benefit from a rule that's easy to state and easy to skip: format each branch of the union identically, with the same column order and the same alias names, even when the underlying tables don't share a naming convention. A reviewer scanning a three-way union should be able to compare column N in each branch by looking straight down, not by re-reading each `SELECT` list to line them up mentally first.

Dialect differences are a smaller trap but a real one for teams working across engines — Postgres, MySQL and Snowflake disagree on identifier quoting (double quotes versus backticks), on whether identifiers are case-sensitive by default, and on trailing-comma tolerance in a column list. A formatting convention that assumes one dialect's rules can silently produce SQL that's merely stylistic in one engine and a syntax error in another, so it's worth confirming a shared convention actually matches the engine a query will run against, not just the engine a formatter defaults to.

The third habit, and the one people skip most, is reformatting a query in its own commit when you're about to make a substantive change to it. A diff that mixes formatting and logic changes is much harder to review than two diffs, even though it's more commits — a reviewer can't tell at a glance whether a changed line is a real behavior change or just re-indentation, so they end up re-reading the whole query to be sure. Splitting the two means the logic-change diff is exactly as long as the logic change.

None of this requires a team-wide tooling rollout to start. Reformatting a query before you paste it into a PR description, a runbook, or a code review comment is a one-person habit that pays off immediately, and it's the exact job the SQL Formatter here does — consistent casing, consistent join and condition indentation, entirely in your browser, so the query in front of a reviewer looks like it was written on purpose.