About SQL Formatter
This SQL query formatter is built for the everyday case: a SELECT, INSERT, UPDATE or DELETE copied out of a log, an ORM debug line or a chat message as one unbroken line. It collapses the whitespace, puts every main clause on a new line, indents AND and OR conditions by two spaces, breaks after each comma, and sets recognised keywords to UPPERCASE or lowercase. Table and column names are left as typed. It works by pattern matching and does not parse SQL, which keeps it fast and dialect-neutral but brings real limits: it does not indent subqueries, and it does not protect the inside of quoted strings or comments. Check the output before running it if your query contains either.
Before and after: formatted SQL examples
Each result is this formatter's exact output with Keyword case set to UPPERCASE unless stated. Line breaks are shown as slashes; AND, OR and items after a comma are indented two spaces in the real output.
Filter with AND and OR
SELECT * FROM orders WHERE status = 'paid' AND total > 100 OR priority = 1
SELECT * / FROM orders / WHERE status = 'paid' / AND total > 100 / OR priority = 1
Join with a sort
select p.name, c.name from products p join categories c on c.id = p.category_id where c.active = 1 and p.price > 20 order by p.name
SELECT p.name, / c.name / FROM products p / JOIN categories c / ON c.id = p.category_id / WHERE c.active = 1 / AND p.price > 20 / ORDER BY p.name
Grouping with HAVING
select department, avg(salary) from employees group by department having avg(salary) > 50000 order by department asc
SELECT department, / AVG(salary) / FROM employees / GROUP BY department / HAVING AVG(salary) > 50000 / ORDER BY department ASC
UPDATE with two assignments
update users set active = 0, updated_at = now() where last_login < '2025-01-01'
UPDATE users / SET active = 0, / updated_at = now() / WHERE last_login < '2025-01-01'
now() is not on the keyword list, so it keeps the case it was typed in.
UNION ALL
select id from a union all select id from b
SELECT id / FROM a / UNION ALL / SELECT id / FROM b
The default sample, with a LEFT JOIN
select u.id, u.name, count(o.id) as orders from users u left join orders o on o.user_id = u.id where u.active = 1 group by u.id order by orders desc limit 10
SELECT u.id, / u.name, / COUNT(o.id) AS orders / FROM users u / LEFT / JOIN orders o / ON o.user_id = u.id / WHERE u.active = 1 / GROUP BY u.id / ORDER BY orders DESC / LIMIT 10
LEFT lands on a line of its own above JOIN. The same happens with RIGHT, INNER and CROSS joins. The query is still valid.
Lowercase keywords
Select Id From Users Where Id Is Null, with Keyword case set to lowercase
select Id / from Users / where Id is null
Keywords are lowered; the identifiers Id and Users keep their capitals.
A string that contains a keyword and a comma
select * from t where note = 'salt and pepper, please'
SELECT * / FROM t / WHERE note = 'salt / AND pepper, / please'
The quoted value has been altered. This is the main case where the output must not be run as it stands.
Which keywords the formatter recognises and what it does with each
Anything not in this table is treated as an ordinary word and left untouched.
| Keywords | Case changed? | Layout |
|---|---|---|
| SELECT, FROM, WHERE, GROUP BY, ORDER BY, HAVING, LIMIT, OFFSET | Yes | Starts a new line |
| INSERT INTO, VALUES, UPDATE, SET | Yes | Starts a new line |
| DELETE FROM | Yes | At the very start of the input FROM drops to the line below DELETE; after an earlier statement DELETE FROM starts a new line and stays together |
| UNION, UNION ALL | Yes | Starts a new line |
| JOIN, ON | Yes | Each starts a new line |
| LEFT JOIN, RIGHT JOIN, INNER JOIN, OUTER JOIN, CROSS JOIN | Yes | New line before the first word, and another before JOIN |
| AND, OR | Yes | New line, indented two spaces |
| AS, IN, NOT, NULL, IS, LIKE, BETWEEN, DISTINCT, ASC, DESC | Yes | Stays in place |
| CASE, WHEN, THEN, ELSE, END | Yes | Stays in place; a CASE expression remains on one line unless it contains AND, OR or a comma |
| COUNT, SUM, AVG, MIN, MAX | Yes | Stays in place |
| Every comma | Not applicable | Line break after it, next item indented two spaces |
| CREATE, ALTER, DROP, WITH, EXISTS, FULL, TOP, RETURNING, OVER, PARTITION BY, and every function other than the five aggregates | No | Not recognised; left as typed |
Because BETWEEN x AND y contains AND, the upper bound moves to its own indented line.
Known limits to check before you rely on the output
- Quoted strings are not protected. Keywords inside a string are recased and can be moved to a new line, and a comma inside a string gets a line break after it. This changes the stored or compared value.
- Comments are not protected. All input is first joined into one line, so a line comment that starts with two dashes can absorb the code that followed it. select a, -- first then b from t on the next line comes back with b inside the comment.
- Commas inside brackets break too. coalesce(nickname, 'n/a') and IN (1, 2, 3) are split across lines the same way a select list is.
- Subqueries are not indented. An inner SELECT stays on the line of its opening bracket, and its FROM and WHERE start at the left margin like the outer ones, so nesting depth is not visible.
- Compound joins split. LEFT JOIN becomes LEFT on one line and JOIN on the next. In LEFT OUTER JOIN or FULL OUTER JOIN, OUTER and JOIN each get a line, while the first word stays on the previous line and is not recased.
- Identifiers that match a keyword are treated as keywords. Quote or rename columns called count, set, end, values, min or max, or fix them by hand afterwards.
- There is one option only: keyword case. Indent width, comma position, line length and dialect cannot be set, and there is no minify mode.
- It does not validate. Broken SQL is laid out without any warning.
SQL clause order: how it is written and when it runs
Putting one clause per line makes a query easier to reason about, because the database does not evaluate the clauses in the order they are written.
| Written order | Clause | Logical evaluation order | What it does |
|---|---|---|---|
| 1 | SELECT | 5 | Chooses and computes the output columns |
| 2 | FROM and JOIN ... ON | 1 | Builds the set of rows from the tables |
| 3 | WHERE | 2 | Filters individual rows |
| 4 | GROUP BY | 3 | Collapses rows into groups |
| 5 | HAVING | 4 | Filters groups, so it can use aggregates |
| 6 | ORDER BY | 6 | Sorts the result |
| 7 | LIMIT and OFFSET | 7 | Keeps a slice of the sorted rows |
This is why a column alias defined in SELECT cannot normally be used in WHERE, yet can be used in ORDER BY.
When a simple formatter is enough, and when it is not
A one-clause-per-line layout is all most people need to read a query quickly. Once each JOIN and its ON condition sit on their own lines, a missing join condition is obvious. Once the WHERE conditions are stacked, a filter on the wrong alias stands out, and so does an OR that needed brackets. In a pull request, a query laid out this way produces line-level diffs, so a reviewer sees which condition changed instead of one rewritten line.
This tool suits flat queries: a select list, a few joins, some conditions, a GROUP BY and an ORDER BY. It is a poor fit for stored procedures, CREATE TABLE scripts, common table expressions, window functions and anything with several levels of nested subqueries, because it has no notion of nesting and does not recognise those keywords. For that kind of SQL, use the formatter in your database client or editor, or a parser-based command-line formatter that you can also run in continuous integration.
Formatting is presentation only. It will not tell you that a query is slow or wrong. For that, run EXPLAIN in your database and read the plan.
How to use it
- 1
Paste your query into the SQL query box. Existing line breaks and extra spaces are removed before formatting, so the input layout does not matter.
- 2
Set Keyword case to UPPERCASE or lowercase.
- 3
Read the result under Formatted SQL: one main clause per line, with conditions and list items indented two spaces.
- 4
Compare any quoted text and comments against your original, then use copy or download to save the result as query.sql.