Unlike JSON, SQL has no canonical serialization, so a formatter is really a bundle of style decisions. The first is clause placement: one major clause per line (SELECT, FROM, WHERE, GROUP BY, ORDER BY), which turns the query's logical pipeline into a vertical outline you can scan. The second is what to do inside a clause — whether each selected column gets its own line, whether JOIN conditions sit on the JOIN line or indent beneath it, and how deep subqueries nest.
Then come the debates with no right answer. Trailing commas (col,) are what most languages use; leading commas (, col) at the start of the next line are a long-standing SQL analyst tradition because commenting out any column — including the last — never breaks the statement, and adding a column produces a one-line diff. Keyword alignment is another split: "river" style right-aligns keywords so SELECT, FROM, and WHERE end at the same column, forming a blank channel down the query (the style popularized by Simon Holywell's sqlstyle.guide), while most autoformatters prefer simple left alignment because it survives edits without re-aligning everything.
The practical value is not aesthetics but canonicalization. When every query passes through the same formatter, textual diffs shrink to semantic diffs, code review comments stop being about style, and two variants of a query can be compared mechanically. Teams that enforce this use linters like SQLFluff, which formats and lint-checks per dialect in CI, exactly as Prettier or Black do for JS and Python.
-- trailing commas -- leading commas (analyst style)
SELECT SELECT
user_id, user_id
email, , email
created_at , created_at
FROM users; -- , last_login ← safe to toggle
FROM users;
UPPERCASE Keywords and the Identifier-Folding Trap
Uppercasing keywords is the oldest convention in SQL, born in an era of monochrome terminals with no syntax highlighting: SELECT and WHERE in capitals were the only way to make structure pop out of a wall of text. The standard does not require it — SQL keywords are case-insensitive everywhere — and now that every editor highlights keywords in color, some modern style guides have swung to all-lowercase. Either is defensible; mixed casing within one codebase is not.
Identifiers are where casing gets dangerous, because databases silently rewrite them. The SQL standard says an unquoted identifier is folded to uppercase, and Oracle and Db2 do exactly that: my_table is stored as MY_TABLE. PostgreSQL deliberately violates the standard and folds to lowercase instead: MY_TABLE becomes my_table. MySQL adds a third behavior — table names map to files, so their case sensitivity depends on the filesystem and the lower_case_table_names setting, a classic source of code that works on macOS and breaks on Linux.
Quoting an identifier ("UserAccounts") suppresses folding and preserves exact case — permanently. Create one quoted mixed-case table in PostgreSQL and every query for the rest of that table's life must quote it, because an unquoted reference folds to useraccounts and misses. This is why a formatter must never touch anything inside identifier quotes or string literals: uppercasing keywords is safe, uppercasing identifiers can literally change which object a query refers to. The boring, portable escape from all of this is snake_case identifiers, unquoted, everywhere.
-- PostgreSQL folds unquoted identifiers DOWN
CREATE TABLE MyTable (id int); -- stored as: mytable
SELECT * FROM MYTABLE; -- works, folds to mytable
-- Oracle folds unquoted identifiers UP
CREATE TABLE MyTable (id int); -- stored as: MYTABLE
-- Quotes freeze the case forever (both systems)
CREATE TABLE "MyTable" (id int);
SELECT * FROM MyTable; -- ERROR: relation not found
SELECT * FROM "MyTable"; -- only this works
Dialect Quirks: ANSI, T-SQL, MySQL, and PostgreSQL Disagree
A formatter cannot even tokenize SQL correctly without knowing the dialect. Identifier quoting alone comes in three flavors: the ANSI standard uses double quotes ("order"), MySQL uses backtick quoting by default (double quotes only work after enabling the ANSI_QUOTES mode), and T-SQL uses square brackets ([order]) alongside double quotes. Feed a bracketed T-SQL identifier to a MySQL-minded formatter and it will treat the brackets as syntax errors or, worse, reflow them.
Pagination is the most visible divergence. MySQL, PostgreSQL, and SQLite use LIMIT 10 OFFSET 20; SQL Server historically used SELECT TOP 10 and now supports OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY (which requires an ORDER BY); the ANSI SQL:2008 form FETCH FIRST 10 ROWS ONLY works in PostgreSQL, Db2, and Oracle 12c and later. String concatenation splits three ways too: || is the standard (PostgreSQL, Oracle, SQLite), T-SQL uses +, and in MySQL you must call CONCAT() because || means logical OR unless PIPES_AS_CONCAT is enabled. Even booleans differ — PostgreSQL has true TRUE/FALSE literals while SQL Server represents them as BIT 1/0.
Comments hold one last trap: -- and /* */ are universal, but # starts a comment only in MySQL, and MySQL's -- requires a following space to count as a comment at all. The practical takeaway when formatting or porting queries: know which dialect you are writing, prefer ANSI forms (FETCH FIRST, ||, standard quotes) where the target supports them, and treat every vendor-specific construct as a portability debt you are knowingly taking on.
-- "Rows 21-30, newest first" in three dialects
-- MySQL / PostgreSQL / SQLite
SELECT id, title FROM posts
ORDER BY created_at DESC
LIMIT 10 OFFSET 20;
-- SQL Server (T-SQL)
SELECT id, title FROM posts
ORDER BY created_at DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- ANSI SQL:2008 (PostgreSQL, Db2, Oracle 12c+)
SELECT id, title FROM posts
ORDER BY created_at DESC
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;
Formatting Long Queries: CTEs, JOINs, and CASE
Formatting rules earn their keep on the queries that hurt: five joins, three levels of subquery, a CASE ladder. The single biggest readability upgrade is converting nested subqueries into common table expressions. A nested query reads inside-out — you must find the innermost SELECT and work backwards — while a WITH chain reads top-down as named steps: raw_events, then sessionized, then daily_totals. Formatted with one CTE per block and a blank line between blocks, the CTE names become the documentation, and each intermediate step can be run and inspected on its own by changing the final SELECT.
JOIN formatting is about making mistakes visible. Put each JOIN on its own line with its ON condition either on the same line (short) or indented directly beneath (long), and never let an ON clause drift so far from its JOIN that you cannot see the pairing. This layout makes the two classic join bugs jump out: a missing ON condition producing an accidental cartesian product, and a filter on the right table of a LEFT JOIN placed in WHERE instead of ON — which silently turns the left join into an inner join because NULLs from unmatched rows fail the WHERE test.
For CASE expressions, put each WHEN ... THEN pair on its own line with the ELSE and END aligned under CASE; a one-line CASE hides both the branch logic and the easy-to-forget fact that a missing ELSE yields NULL. And when a formatted query passes 100 lines or four or five CTEs, treat that as a design signal: a view, a materialized intermediate table, or application-side composition may serve the next reader better than more indentation.
Minified SQL, ORM Logs, and the Debugging Round Trip
The most common reason to format SQL is that a machine wrote it. ORMs and query builders — Hibernate, Prisma, SQLAlchemy, ActiveRecord — log the queries they execute as a single line with positional placeholders like $1 or ?, sometimes thousands of characters long. The debugging loop is mechanical: copy the line from the log, format it here so the clause structure appears, substitute the logged parameter values back into the placeholders, then run it under EXPLAIN ANALYZE in a database client. Formatting is the step that turns an unreadable artifact into something you can reason about; only formatted queries make it obvious that the ORM emitted a needless nested SELECT or joined a table twice.
The reverse operation, minification, exists because queries get embedded: in JSON config files where literal newlines must be escaped, in environment variables, in one-line shell heredocs, in dashboards that store a query per chart. Minifying is also a crude normalizer — collapsing whitespace lets you compare two variants of a query with a text diff.
One honest caution about whitespace collapsing: SQL string literals can contain meaningful runs of spaces, and a naive minifier that applies a global whitespace regex will collapse those too, silently changing WHERE name = 'a b' into a different predicate. After minifying anything that contains string literals, re-check the literals. Finally, remember the boundary of the tool: a formatter proves nothing about correctness. It will happily beautify a query that references a dropped column or filters on the wrong key — validation belongs to the database, a linter like SQLFluff, or your test suite.
-- What the ORM logged (one line, placeholders):
SELECT "u"."id", "u"."email" FROM "users" "u" LEFT JOIN "orders" "o" ON "o"."user_id" = "u"."id" WHERE "u"."created_at" >= $1 AND "o"."status" = $2
-- After formatting + substituting $1, $2:
SELECT "u"."id", "u"."email"
FROM "users" "u"
LEFT JOIN "orders" "o"
ON "o"."user_id" = "u"."id"
WHERE "u"."created_at" >= '2026-01-01'
AND "o"."status" = 'paid' -- ← WHERE on right table:
-- this LEFT JOIN acts as INNER
Last updated:
About this tool
A SQL formatter rewrites a query with consistent indentation, capitalised keywords, and one clause per line so a long SELECT is actually readable. It also offers a minify mode that strips line breaks for embedding queries inside application code or environment variables. Useful for any developer who has stared at a 50-line query someone wrote on a single line.
How to use
Paste the SQL query into the input panel.
Click Format to render an indented, keyword-uppercased version on the right.
Use Minify to collapse the formatted query into a single line for embedding.
Copy the result into your IDE, ORM raw query, BI tool, or migration file.
Iterate until the query reads naturally — formatting helps catch missing JOIN conditions and stray commas.
Common use cases
Cleaning up a complex JOIN before pasting it into a code review.
Formatting an ORM-generated query you copied from a log for debugging.
Preparing a migration script for a Pull Request that follows team style.
Minifying a query for embedding inside an application config string.
Comparing two slightly different queries with a diff tool after formatting both.
Sharing a readable query in documentation or a Slack thread.
Frequently asked questions
Q. Does the formatter validate my SQL?
A. No — it formats syntax structure but does not verify that columns or tables exist or that the query runs. Use a database client or linter for validation.
Q. Will formatting work for non-standard SQL dialects?
A. Mostly. Common keywords across PostgreSQL, MySQL, SQLite, and MSSQL are recognised. Vendor-specific syntax (PIVOT, MERGE) may format with awkward indentation.
Q. Why does my formatter break a CTE in the wrong place?
A. CTEs (WITH clauses) and subqueries are tricky. If the result looks off, hand-tweak after formatting — it is still faster than starting from scratch.
Q. Should I commit formatted SQL to my repo?
A. Yes — consistent SQL formatting in migrations and stored procedures pays off in code review, just like JS/Python formatting.