A Practical SQL Style Guide

SQL is read far more often than it is written: in code review, in incident channels, in query logs at 3 a.m. A consistent style makes a query readable at a glance. These rules are widely used, and each comes with the reason it exists so you can adapt it.

Before and after

Here is a query as it often arrives — from an ORM log, a BI tool or a teammate’s scratch file:

select o.id, o.created_at, c.name as customer, sum(i.qty * i.price) as total from orders o join customers c on c.id = o.customer_id left join order_items i on i.order_id = o.id where o.status in ('paid','shipped') and o.created_at >= '2026-01-01' group by o.id, o.created_at, c.name having sum(i.qty * i.price) > 100 order by total desc limit 20;

And the same query following the rules below:

SELECT
  o.id,
  o.created_at,
  c.name AS customer,
  SUM(i.qty * i.price) AS total
FROM orders AS o
  JOIN customers AS c ON c.id = o.customer_id
  LEFT JOIN order_items AS i ON i.order_id = o.id
WHERE o.status IN ('paid', 'shipped')
  AND o.created_at >= '2026-01-01'
GROUP BY o.id, o.created_at, c.name
HAVING SUM(i.qty * i.price) > 100
ORDER BY total DESC
LIMIT 20;

Nothing about the logic changed. What changed is that each clause starts a line, so you can find the filter, the grouping and the joins without reading every word.

Layout rules

  • One clause per line. SELECT, FROM, each JOIN, WHERE, GROUP BY, HAVING, ORDER BY and LIMIT each begin a new line at the same indentation. The clause keywords form a spine down the left edge.
  • One column per line in long select lists. It makes diffs show exactly which column was added or removed, and it leaves room for a comment per column.
  • Conditions on separate lines, operator first. Starting each line with AND or OR lets you see the boolean structure and comment out a single condition. Wrap mixed AND/OR logic in parentheses even when precedence makes it unnecessary; readers should not need to remember that AND binds tighter.
  • Indent subqueries and CTE bodies one level, with the closing parenthesis aligned with the line that opened it.
  • Spaces around operators and after commas: a = b, qty * price, IN ('paid', 'shipped').
  • Terminate statements with a semicolon and separate statements with a blank line.

Leading or trailing commas? Both are defensible. Trailing commas (a, at the end of the line) read like prose and are the more common choice. Leading commas (, b at the start of the line) make it easy to comment out the last column and keep diffs to one line when columns are appended. Pick one per codebase and let a formatter enforce it.

Keywords, names and aliases

  • Keyword case. Upper-case keywords (SELECT, LEFT JOIN) separate the language from your identifiers, which helps most in editors without highlighting. Some modern teams, including much of the dbt community, prefer all lower case for less visual noise. Consistency matters more than the choice.
  • Identifiers in snake_case, without quotes. Quoted identifiers ("Order Date" in standard SQL and PostgreSQL, backticks in MySQL, brackets in SQL Server) are case-sensitive or dialect-specific and must then be quoted everywhere.
  • Choose singular or plural table names and stick to it. Both order and orders work; mixing them does not. Avoid reserved words such as order or user as names if you can, since they need quoting.
  • Always write AS for column aliases, and use it for table aliases too. An alias without AS is easy to misread as a missing comma: SELECT price total silently renames price.
  • Meaningful table aliases. o for orders and c for customers are fine; a, b, c assigned in FROM order are not.
  • Qualify every column once more than one table is involved. o.created_at keeps the query correct when someone later adds a created_at column to another joined table.

Structure rules

  • Explicit joins. Write JOIN ... ON rather than listing tables separated by commas with join conditions in WHERE. Explicit joins make a forgotten condition obvious, whereas the comma form turns it into an accidental cross join.
  • Prefer CTEs to nested subqueries. A WITH clause names each step, reads top to bottom, and can be tested on its own. Most modern optimisers inline simple CTEs, so there is usually no performance cost; check your database’s behaviour for heavily reused CTEs.
  • Avoid SELECT * in code that ships. It breaks when columns are added or reordered, fetches data you do not need, and hides which columns a query depends on. It is fine for ad-hoc exploration.
  • Name columns in GROUP BY and ORDER BY rather than using positions such as GROUP BY 1, 2, which silently change meaning when the select list is edited.
  • Use ISO 8601 date literals ('2026-01-01') so the meaning does not depend on session locale settings.
  • Comment the why, not the what. -- exclude test accounts created by QA is useful; -- filter status is not. Use -- for single lines and /* */ for longer notes.

Enforcing the style automatically

Style guides that rely on discipline erode. Let a formatter do the work and spend review time on logic. The PasteKit SQL formatter maps the rules above onto options:

  • Dialect — Standard SQL plus MySQL, PostgreSQL, T-SQL, PL/SQL, SQLite, MariaDB, BigQuery, Snowflake, Redshift, Spark SQL, Trino and DB2, so dialect-specific syntax parses correctly
  • Keyword case — UPPER, lower or As written
  • Function case — As written, UPPER or lower
  • Indent style — Standard, or tabular layouts aligned left or right
  • Commas — trailing or leading
  • AND / OR — at the start or the end of the line
  • Blank lines between statements

It runs in your browser, which matters when the query contains customer data in literals or comes from a production log. For a dialect-specific starting point, see the PostgreSQL or T-SQL pages. In a repository, a linter such as SQLFluff can enforce the same conventions in CI. Agree on the settings once, write them down next to the code, and reformat the existing files in a single dedicated commit so that later diffs show only real changes.

Frequently asked questions

Should SQL keywords be uppercase?

It is the most common convention because it separates keywords from identifiers, but lowercase is also popular. Choose one and apply it with a formatter.

Leading or trailing commas in SQL?

Trailing commas are more common and read naturally; leading commas make it easier to comment out the last column and produce cleaner diffs. Both are fine if used consistently.

Are CTEs slower than subqueries?

Usually not. Most modern databases inline simple CTEs. Some engines materialise CTEs in specific cases, so check the query plan for performance-critical queries.

Why avoid SELECT *?

It couples the query to the current column list, fetches unnecessary data and hides dependencies. List the columns you need in production code.

Related