Guides / How to Format SQL Queries

How to Format SQL Queries

4 min read · SQL

Why formatting matters

SQL is written once and read many times — by you, by teammates, and by your future self debugging a production issue at 2 AM. Unformatted SQL is hard to scan, hard to diff, and easy to misread.

Compare these two queries that do the same thing:

-- Unformatted
select u.id,u.name,o.total from users u join orders o on u.id=o.user_id where o.total>100 and u.active=true order by o.total desc limit 10;

-- Formatted
SELECT
  u.id,
  u.name,
  o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.total > 100
  AND u.active = true
ORDER BY o.total DESC
LIMIT 10;

The core rules

1. Uppercase SQL keywords

Write SELECT, FROM, WHERE, JOIN, AND, OR in uppercase. Lowercase for table names, column names, aliases. This visually separates the query structure from your data model.

2. One major clause per line

Each top-level keyword (SELECT, FROM, WHERE, GROUP BY, etc.) starts on its own line, left-aligned.

3. Indent column lists

Columns in the SELECT clause are indented two spaces and listed one per line for three or more columns.

4. Align ON conditions with JOIN

The ON clause of a JOIN goes on the same line, or indented below it for long conditions.

5. Indent AND / OR conditions

Each condition in a WHERE clause goes on its own line, with AND/OR at the start of the line (not the end).

Formatting JOINs

SELECT
  u.id,
  u.name,
  p.plan_name,
  COUNT(o.id) AS order_count
FROM users u
JOIN subscriptions s ON u.id = s.user_id
JOIN plans p ON s.plan_id = p.id
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = true
  AND s.expires_at > NOW()
GROUP BY u.id, u.name, p.plan_name
HAVING COUNT(o.id) > 0
ORDER BY order_count DESC;

Note: each JOIN is at the same indentation level as FROM. The ON condition follows on the same line.

Formatting subqueries

Indent subqueries by two spaces and always give them a meaningful alias:

SELECT
  u.name,
  recent.total_spent
FROM users u
JOIN (
  SELECT
    user_id,
    SUM(amount) AS total_spent
  FROM orders
  WHERE created_at > NOW() - INTERVAL '30 days'
  GROUP BY user_id
) recent ON u.id = recent.user_id
ORDER BY recent.total_spent DESC;

CTEs (WITH clauses)

Common Table Expressions are a cleaner alternative to nested subqueries. They make complex queries much easier to read:

WITH recent_orders AS (
  SELECT
    user_id,
    SUM(amount) AS total_spent
  FROM orders
  WHERE created_at > NOW() - INTERVAL '30 days'
  GROUP BY user_id
),
active_users AS (
  SELECT id, name
  FROM users
  WHERE active = true
)
SELECT
  au.name,
  ro.total_spent
FROM active_users au
JOIN recent_orders ro ON au.id = ro.user_id
ORDER BY ro.total_spent DESC;

Naming conventions in SQL

  • Table names: plural, snake_case (users, order_items)
  • Column names: snake_case (created_at, user_id)
  • Aliases: short but meaningful — u for users, o for orders
  • CTE names: descriptive — recent_orders, active_users
  • Avoid SELECT * in production queries — always name the columns you need

Format SQL instantly

Paste any SQL query and auto-format it with proper indentation and keyword casing.

SQL Formatter →