How the SQL Formatter Works
A SQL formatter changes only whitespace and keyword case, never what the query does. Clause keywords such as SELECT, FROM and WHERE start new lines, lists get one item per line and subqueries are indented. Paste a query above to format, highlight or minify it. String literals, quoted names and comments are kept exactly as written, and nothing leaves your browser.
The query is split into tokens first: keywords, names, numbers, operators, string literals, quoted identifiers and comments. Only the spaces and line breaks between tokens are rebuilt, so the database receives the same statement. Keyword case follows your choice; everything inside quotes is left alone.
Before and After: a Formatted Query
A one line query as it often comes out of an ORM log:
select o.id, c.name, sum(i.qty * i.price) as total from orders o join customers c on c.id = o.customer_id left join order_items i on i.order_id = o.id where o.status = 'shipped' and o.created_at >= '2026-01-01' group by o.id, c.name having sum(i.qty * i.price) > 100 order by total desc limit 20
The same query after Format SQL with the default settings (uppercase keywords, 2 space indent):
SELECT o.id, c.name, SUM(i.qty * i.price) AS total FROM orders o JOIN customers c ON c.id = o.customer_id LEFT JOIN order_items i ON i.order_id = o.id WHERE o.status = 'shipped' AND o.created_at >= '2026-01-01' GROUP BY o.id, c.name HAVING SUM(i.qty * i.price) > 100 ORDER BY total DESC LIMIT 20
With "Clause keywords on their own line" switched off, each keyword keeps its first item on the same line (FROM orders o), which is more compact for short queries.
What the Formatter Changes and Keeps
| Element | Example | Handling |
|---|---|---|
| Keywords | select, left join | Uppercase, lowercase or as typed |
| String literals | 'it''s -- not a comment' | Never changed |
| Quoted names | "Order Date", `order`, [order] | Never changed |
| Comments | -- note, /* note */ | Kept; a line comment ends its line |
| Numbers | 1.5e3, .5, 0x1F | Kept as one token |
| Parameters | $1, :name, @id, ? | Kept as one token |
| Casts and JSON operators | total::numeric, data->>'id' | Cast kept tight, operators spaced |
| Function calls | COUNT(*) | No space before the parenthesis |
| Subqueries and CTEs | IN (SELECT ...) | Indented block, closing parenthesis on its own line |
| CASE | CASE WHEN ... END | One WHEN per line (kept inline inside function calls) |
Column and table names are compared case insensitively by most databases when unquoted, but MySQL table names are case sensitive on Linux. The formatter only changes the case of words it knows as SQL keywords, and function names only when they are followed by a parenthesis, so year as a column name is left alone.
SQL Clause Order: Written vs Run
You write SELECT first, but the database logically evaluates it near the end. That order explains several common errors.
| Written order | Logical order | What it means |
|---|---|---|
| SELECT | 5 | Aliases defined here do not exist yet in WHERE or GROUP BY in standard SQL |
| FROM / JOIN | 1 | Tables are combined first |
| WHERE | 2 | Filters rows; aggregates such as COUNT are not allowed here |
| GROUP BY | 3 | Rows are grouped |
| HAVING | 4 | Filters groups, so aggregates are allowed |
| ORDER BY | 6 | Can use SELECT aliases |
| LIMIT / OFFSET | 7 | Applied last |
Common SQL Mistakes Formatting Makes Visible
- A comma after the last column before FROM. With one column per line it stands out; leading commas make it impossible.
= NULLinstead ofIS NULL. A comparison with NULL is never true, soWHERE phone = NULLreturns no rows.NOT INwith a NULL in the list.x NOT IN (1, NULL)is never true, so the query silently returns nothing. UseNOT EXISTSinstead.- AND and OR without parentheses. AND binds tighter, so
a = 1 OR b = 2 AND c = 3meansa = 1 OR (b = 2 AND c = 3). One condition per line makes this easy to spot. - A filter on the right table of a LEFT JOIN placed in WHERE, which turns it into an inner join. Move the condition into the ON clause.
Formatting helps you read a query; it does not validate it. Run it against your database, or an EXPLAIN, to catch errors. For JSON columns and API payloads, the JSON formatter does the same job.
SQL Formatter Guide
`table`), LIMIT clause, AUTO_INCREMENT. PostgreSQL: double-quote identifiers ("table"), RETURNING clause, ILIKE, window functions. SQLite: minimal syntax, PRAGMA statements. SQL Server (T-SQL): square bracket identifiers ([table]), TOP instead of LIMIT, GO batch separator. The Dialect setting changes how the input is read: MySQL treats a backslash inside a string as an escape and # as the start of a comment, and PostgreSQL reads square brackets as array subscripts rather than quoted names. Every dialect accepts dollar quoted bodies ($$ ... $$) and parameters such as $1, :name, @id and ?.SELECT, FROM, WHERE, JOIN, etc.) can be written in any case: SQL is case-insensitive for keywords. The convention depends on your team's style guide: UPPERCASE is the traditional convention (most SQL books and DBA tools use it), making keywords visually distinct from table and column names. lowercase is increasingly popular in modern teams, especially those who use ORMs. Preserve keeps whatever casing you typed. Many teams that use dbt write lowercase keywords, following the dbt style guide. The most important thing is consistency within your codebase.col1, / col2, / col3. Leading commas put them at the start: col1 / , col2 / , col3. Leading commas make it easier to comment out a column without breaking the syntax (you never leave a trailing comma on the last line). They are common in some database teams, particularly those working with older SQL Server or Oracle codebases. Trailing commas are more standard and what most SQL formatters default to. Both are valid SQL.SELECT * FROM users WHERE name = '${input}' and the user enters '; DROP TABLE users; --, the database may run a second statement that drops the table (on drivers that allow several statements per call). Formatting helps by making query structure visible: poorly structured queries are easier to spot when formatted. The real prevention is parameterized queries (prepared statements): SELECT * FROM users WHERE name = ? with the value passed separately. Never concatenate user input into SQL strings. Use parameterized queries in every language: cursor.execute("SELECT * FROM users WHERE name = %s", (name,)) in Python, db.prepare("SELECT...") in Node.js.function() OVER (PARTITION BY col ORDER BY col ROWS BETWEEN ...). Common window functions: ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), LEAD(), SUM() OVER(), AVG() OVER(). They are supported in PostgreSQL, SQL Server, Oracle, MySQL 8+, and SQLite 3.25+. This formatter keeps each OVER (PARTITION BY ... ORDER BY ...) on one line so a window definition reads as one unit. Use the Highlight view to see the structure clearly.WHERE filters rows before grouping: it operates on individual row values. HAVING filters groups after GROUP BY, it operates on aggregate values. Example: SELECT department, COUNT(*) as count FROM employees WHERE salary > 50000 GROUP BY department HAVING COUNT(*) > 5. Here WHERE removes individual employees with salary under 50K, then GROUP BY groups the remaining employees by department, and HAVING keeps only departments with more than 5 qualifying employees. You cannot use aggregate functions in WHERE; you must use HAVING for that. WHERE executes first and is more efficient as it reduces the data before grouping.INNER JOIN: returns only rows where the join condition matches in both tables. Most common join type. LEFT JOIN: returns all rows from the left table, with matched rows from the right; unmatched right rows are NULL. Use when the left table is the "primary" entity. RIGHT JOIN: opposite of LEFT JOIN; rarely used (you can rewrite as LEFT JOIN). FULL OUTER JOIN: returns all rows from both tables; unmatched rows are NULL on the missing side. CROSS JOIN: returns every combination (cartesian product) of both tables; use with care on large tables. SELF JOIN: joins a table to itself, useful for hierarchies and adjacency lists. Most queries use INNER JOIN and LEFT JOIN. RIGHT JOIN and FULL OUTER JOIN are less common.WITH that you can reference in the main query. Syntax: WITH cte_name AS (SELECT ...) SELECT * FROM cte_name. CTEs improve readability by breaking complex queries into named, logical steps. Recursive CTEs (WITH RECURSIVE) can traverse hierarchical data like organizational charts or bill-of-materials. Multiple CTEs are separated by commas: WITH cte1 AS (...), cte2 AS (...) SELECT .... The formatter indents each CTE body inside its parentheses and starts the main query at the left margin below it. CTEs are supported in PostgreSQL, SQL Server, Oracle, MySQL 8+, and SQLite 3.8.3+. They are not the same as views: CTEs exist only for the duration of the query.SELECT price * qty AS total FROM items WHERE total > 100 fails in standard SQL and PostgreSQL. Repeat the expression (WHERE price * qty > 100) or wrap the query in a subquery or CTE. ORDER BY runs after SELECT, so aliases work there.