SQL Formatter
Format SQL into a readable shape with keyword casing and indented clauses. Keeps string literals and comments intact instead of reformatting inside them.
Paste a query on the left and the formatted result appears here.
Key Takeaways
- Formatting SQL is a tokenizing problem, not a regex problem: a regex formatter turns
WHERE title = 'select'into'SELECT'and changes which rows the query matches. - This tool carries string literals, quoted identifiers and both comment styles (
--and/* */) through byte for byte. - Formatting never changes the query plan; whitespace is meaningless to the database, so the entire benefit goes to the person reading the query.
- Putting each SELECT column on its own line makes diffs precise: adding one column changes one line instead of an entire 200-character row.
The 200-character single line in code review
A pull request contains a one-line change, and that line is 200 characters wide. It holds three joins, four predicates and a subquery. You mean to review it, but first you have to reassemble it mentally while scrolling sideways to find where it ends. Review effectively stops there: nobody reads the line and everybody approves it. Formatting is not a matter of taste, it is what makes a query reviewable at all.
Why regular expressions cannot format SQL
Most formatters on the web run search and replace over raw text, and that leaks in three separate places. A keyword inside a string literal gets uppercased, so the query now matches a different row. A newline inserted into a -- comment either comments out real SQL or uncomments something that was meant to stay hidden. And the '' escape sequence inside a single-quoted string desynchronizes a naive quote scanner, after which every literal and keyword in the rest of the document is misclassified.
-- Inputselect * from posts where title = 'select' and note = 'it''s fine' -- A regex-based formatter (corrupted)SELECT * FROM posts WHERE title = 'SELECT' AND note = 'it''S FINE' -- A tokenizing formatter (correct)SELECT *FROM postsWHERE title = 'select' AND note = 'it''s fine'One fix covers all three failures: tokenize the input once, properly, and treat string literals, quoted identifiers and comments as opaque atoms that pass through untouched. Only tokens classified as keywords are ever case-folded here, and layout decisions look at token boundaries and nothing else. The output is also idempotent, meaning formatting an already-formatted query returns identical text, which is the evidence that the tokenizer can read what it wrote.
Clause structure and better diffs
The formatter recognizes clause openers such as SELECT, FROM, WHERE, the JOIN variants, GROUP BY and ORDER BY, puts each on its own line, and indents its body one level. UNION is deliberately excluded from that indentation, because it separates two queries at the same level and indenting after it would imply a nesting that does not exist. Multi-word openers like LEFT OUTER JOIN are matched before LEFT JOIN, otherwise the shorter match wins and the leftover words land on the wrong line.
The concrete payoff of one column per line shows up in version control. Add a column to a fifty-column list that lives on a single line and the diff marks the whole line as changed, leaving the reviewer to hunt for the difference by eye. With one column per line, the addition is a single green line. The same reasoning applies to predicates separated by AND, which is why they each get their own line too.
Keyword casing and dialect differences
Uppercasing keywords is the common convention because it separates them visually from table and column names. Plenty of teams prefer lowercase, and both are technically correct: no database is case-sensitive about keywords. Consistency matters far more than the particular choice, since mixed casing produces diff noise for no benefit. The formatter only touches words in its keyword list, so a column named status or a schema named public keeps exactly the case you wrote.
| Database | Identifier quoting | Example |
|---|---|---|
| PostgreSQL | Double quotes | "order" |
| MySQL / MariaDB | Backticks | order |
| SQL Server | Square brackets | [order] |
| SQLite | All three accepted | "order" or order |
| Oracle | Double quotes | "ORDER" |
Note
"Users" and users are two different tables. That is precisely why the formatter leaves quoted identifiers alone.Formatting does not affect performance
A recurring worry is that an indented query will run more slowly. It will not. The database parses your text into a plan, and whitespace is discarded in the first step of that parse. A one-line query and its twenty-line formatted equivalent produce the same plan and take the same time. Every bit of the benefit belongs to the human reading the query.
Frequently Asked Questions
- Does formatting affect query performance?
- No. Whitespace and newlines are meaningless to a SQL parser, and the resulting query plan is byte-for-byte identical. The only difference is a few hundred extra characters sent over the network, which never shows up in a measurement.
- Should keywords be uppercase or lowercase?
- Either is correct, since databases do not distinguish. Uppercase is more common because it separates keywords from table and column names visually. What actually matters is picking one convention and holding to it across the team, because mixed casing generates diff noise in the repository.
- Does this validate my SQL?
- No, it is a formatter rather than a validator. It will format a query with a syntax error as best it can. Only your target database can tell you whether a query is valid, since each dialect has its own grammar.
- What about very long queries?
- Queries of several hundred lines are handled comfortably in a single pass in your browser. Deeply nested subqueries do push the indentation to the right, and at that point the real fix is not formatting but splitting the query into CTEs using
WITHblocks. - Will it break my string literals?
- It will not. Single-quoted literals, quoted identifiers and comments are captured by the tokenizer as opaque atoms and written back verbatim. The
''escape sequence is consumed as part of the same literal, so the scanner never falls out of sync.