table of contents feature [open]

How to Format & Beautify Messy SQL Queries

A one-line SQL query next to the same query formatted with one clause per line and indented columns

Last reviewed: October 1, 2026

Short answer: To format a messy SQL query, put each major clause (SELECT, FROM, JOIN, WHERE, GROUP BY, ORDER BY) on its own line, put one column or condition per line, indent the contents of each clause, use one consistent keyword case, and write aliases with AS. Formatting changes only whitespace and letter case of keywords. It does not change what the query returns or how fast it runs.

A formatter can do the layout for you in seconds, but it cannot tell you whether the query is correct. Pick one style, automate it, and keep reviewing the logic yourself.

Most SQL starts life as a quick one-liner typed into a console, copied from a log, or generated by an ORM. For a one-off cleanup, a free, browser-based SQL formatter turns a wall of text into something you can read, and the rest of this guide explains the conventions behind the output so you can choose a style on purpose and apply it by hand when you need to.

Why formatting SQL matters

Formatting matters because SQL is read far more often than it is written, and the database does not care how it looks. Whitespace and line breaks between tokens are ignored by the parser, and the PostgreSQL manual notes that keywords are case-insensitive, so layout is purely for people.

Four practical benefits follow:

  • Readability. When every clause starts a line, you can see at a glance which tables are involved, how they are joined and which filters apply.
  • Code review. Reviewers can comment on one condition or one column, not on a 400-character line.
  • Debugging. With one condition per line you can comment out a single filter or join and rerun the query.
  • Version-control diffs. Line-based diff tools show exactly which column or condition changed. If the query is one line, every edit marks the whole query as changed.

Formatting does not change results or speed. If a "formatted" query behaves differently, something other than whitespace was changed.

A messy query cleaned up step by step

The fastest way to learn the conventions is to apply them to a real one-liner. Here is a typical query as it might appear in a log file:

select o.id,c.name,sum(oi.quantity*oi.unit_price) total from orders o join customers c on c.id=o.customer_id join order_items oi on oi.order_id=o.id where o.status='shipped' and o.created_at>='2026-01-01' group by o.id,c.name having sum(oi.quantity*oi.unit_price)>100 order by total desc limit 10;

Steps 1 and 2: break at every clause, then fix case and spacing

Start a new line at each top-level keyword. Then uppercase the keywords and add spaces around operators and after commas.

SELECT o.id, c.name, SUM(oi.quantity * oi.unit_price) total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
WHERE o.status = 'shipped' AND o.created_at >= '2026-01-01'
GROUP BY o.id, c.name
HAVING SUM(oi.quantity * oi.unit_price) > 100
ORDER BY total DESC
LIMIT 10;

Step 3: one item per line, explicit AS, explicit join type

Stack the columns, move each ON and each extra condition to its own indented line, and write out the optional keywords.

SELECT
    o.id,
    c.name,
    SUM(oi.quantity * oi.unit_price) AS total
FROM orders AS o
INNER JOIN customers AS c
    ON c.id = o.customer_id
INNER JOIN order_items AS oi
    ON oi.order_id = o.id
WHERE o.status = 'shipped'
    AND o.created_at >= '2026-01-01'
GROUP BY
    o.id,
    c.name
HAVING SUM(oi.quantity * oi.unit_price) > 100
ORDER BY total DESC
LIMIT 10;

Checking that nothing changed

Compare the first and last versions token by token. The tables, join conditions, filters, grouping columns, HAVING threshold, sort order and limit are the same. Three kinds of edits were made, and all are neutral:

  • Whitespace and line breaks were added between tokens.
  • Keywords and the function name SUM were uppercased. The string literals 'shipped' and '2026-01-01' were left exactly as they were, because text inside quotes is data and is case-sensitive.
  • The optional keywords AS and INNER were written out. A bare JOIN is an inner join, and an alias with or without AS is the same alias. One caveat: Oracle does not accept AS before a table alias, so there you would keep FROM orders o.

The output column names are still id, name and total. Renaming one would no longer be formatting.

Core formatting conventions

There is no single official SQL style, but the well-known guides agree on most of the basics. The goal is consistency inside one codebase.

Keyword casing

Pick one case for keywords and stick to it. Uppercase keywords with lowercase names is the most widely documented convention: the PostgreSQL manual uses it, and both Simon Holywell's SQL Style Guide and Mozilla's data SQL style guide require uppercase reserved words. Some teams prefer all lowercase. Either works; mixing them does not.

One clause per line, one item per line

Each root keyword starts its own line. Mozilla's guide allows a single argument to stay on the keyword's line and moves multiple arguments onto separate indented lines. That is a sensible rule: FROM orders AS o stays on one line, while a list of five columns is stacked.

Indentation

Indent the contents of a clause one level under its keyword, and use spaces, not tabs, so the query looks the same in every tool. Holywell's guide uses a different layout, a "river": keywords are right-aligned and the details left-aligned. It looks tidy but is harder to maintain by hand.

Commas: trailing vs leading

Trailing commas (at the end of the line) read like normal prose and are what Mozilla's guide specifies. Leading commas (at the start of the next line) make a missing comma easy to spot and let you comment out the last column without touching the line above. Both are valid; choose one.

JOIN and ON

State the join type (INNER JOIN, LEFT JOIN) instead of relying on defaults, and never use comma-separated tables in FROM with the join condition hidden in WHERE. Put ON on its own line, indented one level under the join, as Mozilla's guide describes. Additional join conditions go on further lines starting with AND.

AND and OR placement

Start each new line with the operator, not end the previous line with it. Both guides agree on this. It lets you read down the left edge and see how conditions combine. Because AND binds more tightly than OR, add parentheses whenever both appear, and indent the grouped part:

WHERE o.status = 'shipped'
    AND (
        c.country = 'US'
        OR c.country = 'CA'
    )

Never add or remove parentheses as a "formatting" change unless you are certain of the precedence. That is a logic edit.

Aliases, qualified columns and SELECT *

  • Use AS. Both guides say to always include it. SUM(x) total is easy to misread as two columns with a missing comma; SUM(x) AS total is not.
  • Qualify columns with the table alias whenever a query reads more than one table. It tells the reader where each column comes from and prevents "ambiguous column" errors when a table gains a new column later.
  • Avoid SELECT * in saved queries and application code. Listing columns documents what the query needs and keeps results stable when the table changes. SELECT * is fine for quick exploration.

Line length

Set a limit, commonly somewhere between 80 and 120 characters, so queries fit in a side-by-side diff. Break long lines at a comma or before an operator.

Formatting specific constructs

The same three ideas (new line per clause, one item per line, indent what is nested) cover every construct.

Multi-table JOINs

SELECT
    c.name,
    o.id,
    p.title
FROM customers AS c
INNER JOIN orders AS o
    ON o.customer_id = c.id
LEFT JOIN order_items AS oi
    ON oi.order_id = o.id
    AND oi.quantity > 0
LEFT JOIN products AS p
    ON p.id = oi.product_id
WHERE c.country = 'US';

Subqueries

Open the parenthesis at the end of a line, indent the inner query one level, and close the parenthesis on its own line under the start of the construct.

SELECT
    c.id,
    c.name
FROM customers AS c
WHERE c.id IN (
    SELECT o.customer_id
    FROM orders AS o
    WHERE o.status = 'refunded'
);

CTEs (WITH)

A common table expression gives a subquery a name and moves it to the top, so the main query reads top to bottom. Mozilla's guide prefers CTEs over nested subqueries for this reason.

WITH recent_orders AS (
    SELECT
        customer_id,
        COUNT(*) AS order_count
    FROM orders
    WHERE created_at >= '2026-01-01'
    GROUP BY customer_id
)
SELECT
    c.name,
    r.order_count
FROM customers AS c
INNER JOIN recent_orders AS r
    ON r.customer_id = c.id
ORDER BY r.order_count DESC;

CASE expressions

Put each WHEN on its own line, indent them under CASE, and align END with CASE. Always alias the result.

SELECT
    id,
    CASE
        WHEN total >= 1000 THEN 'large'
        WHEN total >= 100 THEN 'medium'
        ELSE 'small'
    END AS order_size
FROM orders;

Window functions

SELECT
    customer_id,
    id,
    total,
    ROW_NUMBER() OVER (
        PARTITION BY customer_id
        ORDER BY created_at DESC
    ) AS recency_rank
FROM orders;

INSERT, UPDATE and DELETE

INSERT INTO customers (name, email, country)
VALUES
    ('Ada Park', 'ada@example.com', 'US'),
    ('Luis Ortega', 'luis@example.com', 'MX');

UPDATE orders
SET
    status = 'cancelled',
    updated_at = CURRENT_TIMESTAMP
WHERE id = 1042;

DELETE FROM sessions
WHERE expires_at < CURRENT_TIMESTAMP;

Always list the target columns in an INSERT. Give the WHERE of an UPDATE or DELETE its own line so that a missing filter is obvious in review.

Long IN lists

Short lists stay inline: WHERE status IN ('new', 'paid'). Stack longer lists:

WHERE country IN (
    'US',
    'CA',
    'MX',
    'BR'
)

UNION

Put the set operator on its own line, with a blank line above and below, and format each branch as a complete query.

SELECT email
FROM customers

UNION ALL

SELECT email
FROM newsletter_signups;

UNION removes duplicate rows and UNION ALL keeps them. A formatter must never swap one for the other.

Naming and comments

Names and comments are not whitespace, so a formatter will not fix them, but they decide how readable the result is.

  • Use lowercase snake_case (first_name, created_at). Both guides recommend it and reject camelCase. Lowercase unquoted names also avoid the case-folding surprises described in the dialect section below.
  • Avoid reserved words such as order, user or select as names.
  • Be consistent about plural or singular table names. Holywell's guide prefers collective or plural table names and singular column names. Other teams use singular tables. Pick one.

For comments, SQL has two forms: -- to the end of the line and /* ... */ for blocks. Holywell's guide suggests the block form where possible. Comment the reason, not the syntax: "exclude test accounts created by QA" helps; "filter rows" does not.

Published style guides compared

If your team has no style yet, adopt a published one instead of inventing your own. The table compares two public guides with a common team variant.

ConventionHolywell (sqlstyle.guide)Mozilla data docsCommon team variant
Keyword caseUppercaseUppercaseAll lowercase, relying on editor highlighting
IdentifiersLowercase with underscoresLowercase with underscoresSame
LayoutRight-aligned keywords forming a "river"Left-aligned root keywords, contents indented one levelLeft-aligned, 2 or 4 spaces
CommasSpace after each commaAt the end of the lineLeading commas
AND / ORNew line before eachAt the beginning of the lineSame
AliasesAlways use ASAlways use ASSame
JoinsIndented to the far side of the riverAlways state the join type; ON or USING on a new indented lineSame as Mozilla
Nested queriesSubqueries aligned to the riverPrefer CTEs over nested subqueriesCTEs for anything reused or deep
CommentsBlock comments where possible, otherwise two dashesNot a focus of the guideTwo dashes for short notes

Dialect differences: quoting and case

Layout rules are portable, but quoting is not. Each database quotes identifiers its own way, and a formatter set to the wrong dialect can misread your query. The portable rule: single quotes for strings, and names that never need quoting.

DatabaseIdentifier quotesString quotesWorth knowing
PostgreSQLDouble quotesSingle quotes; also dollar-quoted stringsUnquoted names are folded to lowercase; quoted names are case-sensitive
MySQLBacktick (`name`); double quotes only when ANSI_QUOTES mode is onSingle or double quotes; single only when ANSI_QUOTES is onBackslash escapes such as \' are accepted inside strings
SQL ServerSquare brackets, or double quotes when QUOTED_IDENTIFIER is ONSingle quotes (recommended by Microsoft)Prefix Unicode strings with an uppercase N: N'text'
SQLiteDouble quotes; also accepts brackets and backticks for compatibilitySingle quotesMisused quotes are sometimes tolerated; the docs warn not to rely on that
OracleDouble quotesSingle quotes; also the q'[...]' alternative quoting formUnquoted names are stored in uppercase; quoted names are case-sensitive

Three consequences for formatting:

  • Never change the case of anything inside quotes. In PostgreSQL, "Orders" and orders are different tables. In Oracle, "employees" and employees are different names, because the unquoted one becomes EMPLOYEES.
  • Escape a single quote in a string by doubling it: 'O''Brien'. PostgreSQL, MySQL, SQL Server and Oracle all document this form.
  • Tell the formatter which dialect you use. Double quotes mean an identifier in PostgreSQL and Oracle but can mean a string in MySQL.

Manual vs automatic formatting

Format by hand while you write, and let a tool enforce the style afterwards.

  • Online formatter. Best for a single query pasted from a log, a ticket or ORM debug output.
  • Editor or IDE formatter. Most database clients and code editors have a built-in or plug-in SQL formatter. Good for daily work; check that everyone shares the same settings.
  • Command-line linter and formatter. These tools read a configuration file from the repository, report style violations and can rewrite files. SQLFluff is one example: its documentation describes it as a dialect-flexible SQL linter with a fix command and support for templated SQL such as Jinja and dbt.

Enforcing style in a team

  1. Agree on a style and commit the formatter configuration to the repository.
  2. Reformat the existing SQL once, in a dedicated commit with no logic changes, so later diffs stay clean.
  3. Run the formatter before each commit. The pre-commit framework, for example, manages Git hooks through a .pre-commit-config.yaml file and runs them on staged files.
  4. Run the same check in CI and fail the build on violations, so nothing unformatted is merged.

Safety: what a formatter does not do

A formatter rearranges text. It does not check that your query is right, safe or fast.

  • It does not validate logic. A wrong join condition or a missing WHERE looks just as neat as a correct one.
  • Review the output. A tool that misreads your dialect can split an operator or damage a quoted name. Run your tests after a bulk reformat.
  • Do not paste secrets or customer data into tools you do not trust. Queries copied from logs often contain literal emails, names or tokens. Replace them with placeholders first.

Parameterized queries are part of clean SQL

Clean SQL keeps code and data separate. The OWASP SQL Injection Prevention Cheat Sheet lists prepared statements with parameterized queries as its first defense and tells developers to stop building dynamic queries with string concatenation. Compare:

-- Unsafe: user input is glued into the SQL text
query = "SELECT id, name FROM customers WHERE email = '" + email + "'"

-- Safe: the SQL text is fixed and the value is bound separately
SELECT
    id,
    name
FROM customers
WHERE email = ?;

The placeholder syntax depends on the driver (?, :email, @email or $1). The parameterized version is also easier to format and review, because the query text never changes. Table and column names cannot be bound as parameters; OWASP advises mapping any such input to a fixed list of allowed names.

Common formatting mistakes

MistakeWhy it hurtsFix
Whole query on one lineUnreadable; every edit shows as a full-line diffOne clause per line, one item per line
Alias without ASEasily mistaken for a missing commaAlways write AS for column aliases
Comma joins with conditions in WHEREJoin logic and filters are mixed; easy to create an accidental cross joinExplicit JOIN ... ON
AND and OR mixed without parenthesesAND is evaluated first, which may not be what the layout suggestsAdd parentheses and indent the group
Uppercasing or re-quoting identifiers and stringsCan point at a different object or change a comparisonChange case of keywords only
Reformatting and changing logic in one commitReviewers cannot see the real changeSeparate commits

Checklist

  • Each clause starts on its own line.
  • One column, join or condition per line; nested parts are indented.
  • Keywords use one consistent case.
  • Join types are explicit and ON is on its own indented line.
  • AND and OR start lines, with parentheses where they are mixed.
  • Every column alias uses AS; columns are qualified in multi-table queries.
  • No SELECT * in saved or application queries.
  • No literal secrets or personal data in the query text; values are passed as parameters.
  • Results were compared or tests were run after any bulk reformat.

FAQ

Does formatting SQL change the query results or performance?

No. Formatting only changes whitespace, line breaks and the case of keywords, none of which affect how the database parses or plans the query. If results change, something else was edited, such as parentheses, quoting or an alias name.

Should SQL keywords be uppercase or lowercase?

Either is valid because keywords are case-insensitive. Uppercase keywords with lowercase names is the most widely documented convention and the one used in the PostgreSQL manual and in the Holywell and Mozilla guides. What matters is that the whole codebase uses one choice.

Are leading commas or trailing commas better in SQL?

Neither is objectively better. Trailing commas read naturally and are used in most published guides. Leading commas make a missing comma easier to see and let you comment out the last column cleanly. Choose one and let a formatter enforce it.

How do I format a long SQL query that is on one line?

Paste it into a formatter with the correct dialect selected, or do it by hand in three passes: break at each clause keyword, normalize keyword case and spacing, then put one column or condition per line and indent. Compare the result with the original before you replace it.

Is it safe to paste SQL into an online formatter?

It depends on what the query contains and how the tool works. Remove passwords, tokens, customer data and internal host names first. For sensitive code, use a tool that runs locally or one you have reviewed and trust.

Will a SQL formatter find errors in my query?

Not reliably. A formatter is not a validator. It may fail on badly broken syntax, but it will not notice a wrong join, a missing filter or a typo in a column name. Use the database itself, tests and a linter for that.

How do I format SQL that is embedded in application code?

Keep it in a multi-line string or a separate .sql file, format it like any other query, and pass values as bound parameters. Avoid building the text with concatenation; it is harder to read, cannot be formatted by tools and is the root cause of SQL injection.

Why did my query break after formatting?

The usual cause is a dialect mismatch: the tool misread quoting, an operator or a vendor-specific construct. Select the right dialect, and never let a formatter change text inside quotes.

Bottom line

Readable SQL comes from a few habits: one clause per line, one item per line, consistent keyword case, explicit joins and aliases, and names that never need quotes. None of it changes what the query does, which is why it is safe to automate. Use an online SQL formatter for one-off cleanups and a shared, automated check for team code, then spend your review time on the logic. You can find more free developer tools for related tasks.

Sources referenced in this guide

Previous Post Next Post