ToolSite
All posts

How to Format Messy SQL Queries for Readability

Turn messy one-line SQL into readable code with rules for uppercase keywords, consistent indentation, CTE layout, subquery and JOIN formatting, with examples.

By ToolSite6 min readguides

Why SQL Formatting Matters

SQL is the only language where a 200-line query written as a single line is common in production. Developers copy queries from application logs, ORM query inspectors, or database consoles and get a wall of text:

SELECT u.id,u.name,u.email,o.id,o.total,o.created_at FROM users u LEFT JOIN orders o ON u.id=o.user_id WHERE o.created_at > '2026-01-01' AND o.status = 'completed' AND u.active = true ORDER BY o.created_at DESC LIMIT 50

This is valid SQL. It is also unreadable. Formatting transforms it into something a human can reason about. When a query breaks, the first step is always to format it. You can't debug what you can't read.

The Core Rules

Uppercase Keywords

SQL keywords (SELECT, FROM, WHERE, JOIN, AND, ORDER BY) should be uppercase. This separates the structure of the query from the data (table names, column names, values):

SELECT u.id, u.name, u.email
FROM users u
WHERE u.active = true;

Lowercase keywords are valid but harder to scan. Uppercase is a convention shared by nearly every SQL style guide. Your eyes learn to skip over the uppercase words and focus on the changing parts: column names, conditions, and values.

One Clause Per Line

Every major clause starts on a new line, left-aligned:

SELECT ...
FROM ...
JOIN ...
  ON ...
WHERE ...
  AND ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...

The query reads top-to-bottom as a logical flow: select these columns, from these tables, joined on this condition, filtered by these criteria, grouped, ordered, limited.

When you come back to a query six months later, you should be able to scan the left margin and understand its structure instantly. No hunting for where the WHERE ends and the ORDER BY begins.

Indentation for Sub-Elements

Columns in SELECT are indented. Join conditions are indented under JOIN. AND clauses are indented under WHERE:

SELECT
  u.id,
  u.name,
  u.email,
  o.total
FROM users u
LEFT JOIN orders o
  ON u.id = o.user_id
WHERE o.created_at > '2026-01-01'
  AND o.status = 'completed'
  AND u.active = true
ORDER BY o.created_at DESC
LIMIT 50;

A 2-space or 4-space indent works. Pick one and apply it everywhere. Inconsistency is worse than the wrong choice.

One Column Per Line for SELECT with 3+ Columns

-- Good for 1-2 columns
SELECT id, name FROM users;

-- Good for 3+ columns
SELECT
  id,
  name,
  email,
  created_at,
  last_login
FROM users;

When a SELECT has 8 columns with aggregate functions and aliases, putting them all on one line makes it impossible to spot that SUM(o.total) is missing. One column per line makes every column visible and every omission obvious.

Comma Placement

There are two schools. Leading commas:

SELECT
  id
  , name
  , email
FROM users;

Trailing commas:

SELECT
  id,
  name,
  email
FROM users;

Leading commas make it easier to add and remove lines without trailing-comma errors. Trailing commas are more common and look more like other languages. Pick one and apply it consistently. Mixing them is the only bad choice.

Common Table Expressions (CTEs)

CTEs benefit from careful formatting. Each CTE gets its own indented block:

WITH recent_users AS (
  SELECT id, name
  FROM users
  WHERE created_at > '2026-01-01'
),
user_orders AS (
  SELECT
    u.name,
    COUNT(o.id) AS order_count
  FROM recent_users u
  LEFT JOIN orders o ON u.id = o.user_id
  GROUP BY u.name
)
SELECT *
FROM user_orders
WHERE order_count > 0
ORDER BY order_count DESC;

Place each CTE name on its own line. Open the parentheses on the same line as the CTE name. Indent the content by 2 spaces. Close the parentheses at the same indent level as the CTE name, followed by a comma for all CTEs except the last. The final SELECT is at the top level, not indented.

For long CTE chains (5+ CTEs), add a comment above each CTE explaining what it produces. Future you will thank past you.

Subqueries

Subqueries should be indented and clearly delimited with parentheses on their own lines:

SELECT name
FROM users
WHERE id IN (
  SELECT user_id
  FROM orders
  WHERE total > 100
);

A subquery on one line is fine when it fits. A 15-line subquery buried inside a WHERE clause should be extracted as a CTE. CTEs give the subquery a name and make the outer query read like plain English.

Correlated Subqueries

When a subquery references a column from the outer query, indent it clearly:

SELECT
  u.name,
  (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;

Correlated subqueries can be slow on large tables. The inner query runs once per outer row. Consider rewriting as a LEFT JOIN with GROUP BY if the dataset is large.

JOIN Formatting

Always use explicit JOIN syntax, never comma-separated tables in FROM:

-- Bad: implicit join
SELECT u.name, o.total
FROM users u, orders o
WHERE u.id = o.user_id;

-- Good: explicit join
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id;

The ON clause is indented under the JOIN. For multiple joins, align the JOIN keywords at the same level:

SELECT u.name, o.total, p.name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id;

For complex ON conditions with multiple predicates:

JOIN orders o
  ON u.id = o.user_id
  AND o.status = 'completed'
  AND o.created_at > '2026-01-01'

CASE Expressions

SELECT
  name,
  CASE
    WHEN total > 1000 THEN 'premium'
    WHEN total > 500 THEN 'standard'
    ELSE 'basic'
  END AS tier
FROM users;

CASE and END at the same level. Each WHEN/ELSE indented. The column alias follows END.

Window Functions

SELECT
  name,
  department,
  salary,
  RANK() OVER (
    PARTITION BY department
    ORDER BY salary DESC
  ) AS dept_rank
FROM employees;

The OVER clause opens on the same line. PARTITION BY and ORDER BY inside OVER are indented.

Use the Formatter

The SQL Formatter applies these rules automatically. Paste any messy query and get consistently formatted output in your preferred SQL dialect. It handles:

  • Uppercase keywords
  • Consistent indentation (2 or 4 spaces)
  • Column-per-line for multi-column SELECTs
  • BETWEEN, IN, and CASE expression formatting
  • Dialect-specific quoting (backticks, double quotes, brackets)

Try it yourself: open the SQL Formatter. Paste select id,name,email from users where active=true order by name and click Format. The output is structured, indented, and readable. Try a complex query with JOINs, subqueries, and a CTE to see how nesting is handled. Switch between 2-space and 4-space indentation.

Related Reading