</>CodeScrub

SQL Formatting Best Practices for Teams

A practical SQL formatting standard your team can adopt today. Covers keyword casing, indentation, JOIN alignment, and naming conventions with before/after examples.

8 min read
  • sql
  • formatting
  • best-practices
  • style-guide

title: "SQL Formatting Best Practices for Teams" description: "A practical SQL formatting standard your team can adopt today. Covers keyword casing, indentation, JOIN alignment, and naming conventions with before/after examples." date: "2026-06-01" tags: ["sql", "formatting", "best-practices", "style-guide"] relatedTool: "sql-formatter" published: true

Unformatted SQL is a code-review bottleneck. Everyone writes queries slightly differently — uppercase or lowercase keywords, tabs or spaces, JOINs on the same line or new ones, leading or trailing commas — and every difference invites a nitpick that has nothing to do with correctness. What follows is an opinionated, ready-to-adopt SQL formatting standard. Pick it up as-is, adapt it to your team, or use it as the starting point for the "how we write SQL" doc you keep meaning to write. Either way, stop arguing about style and start writing queries a colleague can read six months from now.

The Core Rules

Seven rules, each with a before/after so you can see the difference on real code. In descending order of how much they matter.

Rule 1: Uppercase SQL keywords

Before:

select id, name from users where active = true

After:

SELECT id, name FROM users WHERE active = true

Why: uppercase keywords visually separate reserved words from identifiers. The eye can pattern-match SELECT ... FROM ... WHERE at a glance without parsing the whole line. This is the single most widely adopted convention across the industry — every major style guide, every ORM's raw-SQL logging, every DBA blog post uses it. Reserve lowercase for column names, table names, and everything the database is storing. Do not mix cases in a single keyword (Select, From) — that reads as neither convention and slows the eye down.

Rule 2: One clause per line

Before:

SELECT id, name FROM users WHERE active = true ORDER BY name

After:

SELECT id, name
FROM users
WHERE active = true
ORDER BY name

Why: every top-level clause — SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT — gets its own line. Diffs stay clean when a single clause changes, and a query's structure is visible from the shape alone before you read the details.

Rule 3: Indent column lists and conditions

Before:

SELECT
id,
name,
email,
created_at
FROM users
WHERE
active = true
AND role = 'admin'

After:

SELECT
  id,
  name,
  email,
  created_at
FROM users
WHERE
  active = true
  AND role = 'admin'

Why: indentation shows hierarchy. Columns belong to SELECT, conditions belong to WHERE. Without the indent the eye can't tell where one clause ends and the next begins. Two spaces is enough; four is defensible; tabs are contentious for the same reason they always are — just make sure your team picks one and your formatter enforces it.

Rule 4: Leading commas or trailing commas — pick one

Trailing (the more common style, and what most auto-formatters produce):

SELECT
  id,
  name,
  email

Leading (the pragmatic style — commenting out any single column doesn't break the query):

SELECT
  id
  , name
  , email

Both are defensible. Trailing is more idiomatic and reads more naturally on a first look. Leading has a real day-to-day benefit: comment out the -- , email line in a debugging session and the query still parses; do the same to a trailing-comma line and it breaks. I've seen teams argue about this for weeks. Don't. Pick one, put it in your style guide, and enforce it with your formatter. The correctness cost of not picking is higher than the cost of picking the "wrong" one.

Rule 5: Each JOIN on its own line, with the ON condition indented

Before:

SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id LEFT JOIN payments p ON o.id = p.order_id

After:

SELECT
  u.name,
  o.total
FROM users u
INNER JOIN orders o
  ON u.id = o.user_id
LEFT JOIN payments p
  ON o.id = p.order_id

Why: each JOIN is doing distinct semantic work — bringing in a new table under specific conditions. Giving each its own line, with the ON condition indented under it, makes the join graph obvious. When something goes wrong (missing rows, duplicates, cartesian product), you can point at exactly one line as the suspect. Prefer explicit INNER JOIN over bare JOIN even though they mean the same thing — the reader shouldn't have to remember the default, and mixing the two in one query looks accidental.

Rule 6: Use meaningful table aliases

Before:

SELECT a.name, b.total FROM users a JOIN orders b ON a.id = b.user_id

After:

SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id

Why: u for users, o for orders, p for products, oi for order_items. Single-letter (or two-letter) aliases are fine — they save typing and don't hurt readability — as long as they map to the first letter(s) of the table name. Never use a, b, c. In a five-table join, a.id = b.user_id AND b.id = c.order_id becomes an unreadable puzzle; u.id = o.user_id AND o.id = oi.order_id reads itself. This is the fastest formatting win most teams can make.

Rule 7: Prefer CTEs over nested subqueries

Before:

SELECT * FROM (
  SELECT user_id, COUNT(*) AS order_count FROM (
    SELECT * FROM orders WHERE status = 'completed'
  ) completed_orders
  GROUP BY user_id
) user_orders WHERE order_count > 5

After:

WITH completed_orders AS (
  SELECT *
  FROM orders
  WHERE status = 'completed'
),
user_orders AS (
  SELECT
    user_id,
    COUNT(*) AS order_count
  FROM completed_orders
  GROUP BY user_id
)
SELECT *
FROM user_orders
WHERE order_count > 5

Why: CTEs read top-to-bottom, the same direction humans read code. Nested subqueries read inside-out, which is how the query engine executes them but not how anyone thinks. Every CTE becomes a named, reusable chunk you can debug in isolation — swap the final SELECT for SELECT * FROM completed_orders LIMIT 10 and you're inspecting an intermediate step in seconds. On modern engines (Postgres 12+, Snowflake, BigQuery, Redshift, MySQL 8+), there is no meaningful performance penalty for CTEs over subqueries in the common cases.

Naming Conventions

Formatting is only half the battle — how you name things matters just as much. Use these defaults unless your existing schema has locked in something different:

| Element | Convention | Example | | --- | --- | --- | | Tables | snake_case, plural | users, order_items | | Columns | snake_case | first_name, created_at | | Aliases | Lowercase, abbreviated | u, o, oi | | CTEs | snake_case, descriptive | active_users, monthly_revenue | | Boolean columns | is_ / has_ prefix | is_active, has_paid | | Timestamp columns | _at suffix | created_at, updated_at, deleted_at | | Foreign keys | <referenced_table_singular>_id | user_id, product_id | | Primary keys | id, not <table>_id | id on users, not user_id |

Consistency matters more than which specific convention you pick. If your codebase already uses camelCase for columns, don't start mixing styles in new tables just because a blog post said so — the transition cost of migrating a live schema is almost always higher than the aesthetic cost of a mixed convention. Save the migration for the next major version, and in the meantime document the exception clearly so new hires don't ping you about it every quarter.

Enforce It Automatically

Formatting standards die the moment they depend on humans catching every violation in code review. Move enforcement to tooling:

  • On demand — paste any query into the CodeScrub SQL Formatter, pick your dialect (PostgreSQL, MySQL, T-SQL, BigQuery, and six more), and copy the formatted output.
  • In your editor — VS Code's Prettier SQL extension and JetBrains' built-in SQL formatter both work on file save, so the query is already correct when the reviewer sees it.
  • In CIsqlfluff (Python) is the de-facto linter; sqlfmt gives you Prettier-style opinionated formatting with zero configuration. Add either to your .github/workflows or pre-commit config and merge blocks the moment someone forgets.

Any one of these is better than none. Pick the layer that fits your team's workflow and don't relitigate the rules once the formatter is deciding for you.

Bottom line

Readable SQL reduces bugs, speeds up code review, and shortens onboarding — the same person picks up a well-formatted query in a fraction of the time it takes them to parse a wall of lowercase noise. Adopt a standard (this one is fine), enforce it with the CodeScrub SQL Formatter or the linter of your choice, and free your team to argue about the queries themselves rather than their punctuation.

Ad space · blog-article-mid