How to Format a SQL Query: Readable Layout Rules, Formatter Pitfalls and Date Formatting
SQL does not care about whitespace, which is why queries copied from logs, ORMs and BI tools arrive as a single 900-character line. Formatting puts each clause on its own line and indents what belongs together, so the shape of the query shows what it does. A formatter does this in one click. What remains for you is choosing conventions a team can agree on, and knowing the few cases where reformatting changes more than whitespace.
From one line to a readable query
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.created_at>='2026-01-01' and o.status in ('paid','shipped') group by o.id,c.name having sum(i.qty*i.price)>100 order by total desc limit 20
SELECT
o.id,
c.name,
SUM(i.qty * i.price) AS total
FROM orders AS o
JOIN customers AS c
ON c.id = o.customer_id
LEFT JOIN order_items AS i
ON i.order_id = o.id
WHERE o.created_at >= '2026-01-01'
AND o.status IN ('paid', 'shipped')
GROUP BY o.id, c.name
HAVING SUM(i.qty * i.price) > 100
ORDER BY total DESC
LIMIT 20;
The formatted version answers the reader's questions in order: what columns come back, from which tables, how the tables connect, which rows are filtered, and how the result is grouped and sorted. The LEFT JOIN now stands out, and that matters, because it changes which orders appear.
Layout rules that hold up in review
One major clause per line, starting at the left margin: SELECT, FROM, each JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. Indent everything that belongs to a clause.
One selected column per line once there are more than three or four. A diff then shows exactly which column was added or removed, instead of one changed line holding twelve columns.
Put join conditions under their join, and AND/OR at the start of each condition line. Leading operators make it obvious how conditions combine, and parentheses around mixed AND/OR groups prevent the classic precedence bug where AND binds tighter than you meant.
Keywords in upper case and identifiers in lower case is the most common convention, and the one most formatters default to. The SQL Style Guide by Simon Holywell argues for it. What matters more than which convention you pick is that the whole codebase uses the same one, enforced by a tool.
Always alias with AS and use short but meaningful aliases (o for orders, c for customers). Qualify every column in a multi-table query, because an unqualified name that exists in only one table today becomes ambiguous when someone adds a column with the same name elsewhere.
CTEs and subqueries
Long queries read best as named steps. A common table expression (WITH) turns nested subqueries into a sequence you can read from top to bottom.
WITH paid_orders AS (
SELECT id, customer_id, created_at
FROM orders
WHERE status = 'paid'
),
first_orders AS (
SELECT customer_id, MIN(created_at) AS first_order_at
FROM paid_orders
GROUP BY customer_id
)
SELECT
DATE_TRUNC('month', first_order_at) AS cohort,
COUNT(*) AS new_customers
FROM first_orders
GROUP BY cohort
ORDER BY cohort;
Each CTE gets a name that says what it contains, its own indented block, and a blank line or comma-separated break between steps. Comma placement, leading or trailing, is a style choice. Leading commas make it easier to comment out the last column; trailing commas read more naturally. Pick one.
What a formatter can break
Formatting is meant to change whitespace only, but a few cases need care.
Line comments in minified SQL. -- comments run to the end of the line. If a query containing -- skip archived was collapsed onto one line before you got it, everything after the comment is already commented out, and the formatter cannot know which part was code. Formatting a correctly structured query is safe; reconstructing a flattened one is not.
Dialect-specific syntax. A formatter set to generic SQL may mangle PostgreSQL's :: casts or $$ function bodies, MySQL backtick identifiers, T-SQL square brackets and GO batch separators, or BigQuery's backtick-quoted project.dataset.table names. Choose the matching dialect. The open-source sql-formatter library, used by many online tools, supports more than a dozen dialects.
String literals and quoted identifiers must never be touched. A formatter that upper-cases keywords must leave 'select' inside a string and "Order" as a quoted identifier alone. Check any new tool on a query containing both.
Templated SQL, with Jinja in dbt, ${} placeholders or ? and :name parameters, may confuse formatters that do not understand the template syntax. SQLFluff supports dbt templates, both for linting and fixing.
Formatting in your editor and pipeline
DBeaver Ctrl+Shift+F (Cmd+Shift+F on macOS)
DataGrip Ctrl+Alt+L (Cmd+Alt+L)
VS Code Shift+Alt+F with a SQL formatter extension installed
SSMS no built-in formatter; use an add-in or an external tool
# command line / CI
npx sql-formatter -l postgresql query.sql
sqlfluff fix --dialect postgres models/
pg_format query.sql # pgFormatter, PostgreSQL
An online formatter is quickest for a one-off query from a log. For a codebase, run the same formatter in CI so formatting never shows up in code review. Queries often contain customer data in literals and table names that reveal your schema, so prefer tools that format in the browser without sending the query to a server.
SQL datetime format in query output
"Format" also often means presenting a timestamp column in a particular way. Every dialect has its own function, and the format codes differ.
-- PostgreSQL
SELECT to_char(created_at, 'YYYY-MM-DD HH24:MI') FROM orders;
-- MySQL / MariaDB
SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:%i') FROM orders;
-- SQL Server
SELECT CONVERT(varchar(16), created_at, 120) FROM orders; -- yyyy-mm-dd hh:mi
SELECT FORMAT(created_at, 'yyyy-MM-dd HH:mm') FROM orders; -- slower, .NET format codes
-- SQLite
SELECT strftime('%Y-%m-%d %H:%M', created_at) FROM orders;
Format dates for display only. Filter and join on the native date type: WHERE created_at >= '2026-01-01' can use an index, while WHERE to_char(created_at, 'YYYY') = '2026' usually cannot. And keep output in ISO 8601 (YYYY-MM-DD) when it goes to another program, because it sorts correctly as text and is unambiguous across locales.
Sources and further reading
Simon Holywell's SQL Style Guide (sqlstyle.guide) describes a widely used convention. The documentation for the sql-formatter library, SQLFluff and pgFormatter lists supported dialects and options. Date functions are documented in the PostgreSQL manual ("Data Type Formatting Functions"), the MySQL reference (DATE_FORMAT), Microsoft's T-SQL reference (CAST and CONVERT, FORMAT) and the SQLite date and time functions page.
Frequently asked questions
How do I format a SQL query?
Paste it into a SQL query formatter, choose your database dialect, and apply it; or use your editor's format shortcut. By hand: one clause per line, indent columns and conditions, put each join with its `ON` condition, and use consistent keyword case.
Should SQL keywords be upper case?
It is the most common convention and makes keywords easy to tell apart from identifiers, but SQL itself does not care. Consistency across the codebase, enforced by a formatter, matters more than the choice.
Can formatting a SQL query change its result?
Formatting whitespace cannot. Problems come from a flattened query whose `--` comment swallowed code, from a formatter using the wrong dialect, or from a tool that alters string literals. Review the diff the first time you use a new formatter.
Is there a SQL formatter for SSMS?
SQL Server Management Studio has no full built-in formatter. Use an add-in, an external or online formatter set to the T-SQL dialect, or format in Azure Data Studio or VS Code with a SQL extension.
How do I format a date in a SQL query?
Use the dialect's function: `to_char` in PostgreSQL, `DATE_FORMAT` in MySQL, `CONVERT` or `FORMAT` in SQL Server, and `strftime` in SQLite. Format only in the SELECT list; filter on the native date type so indexes are used.
Leading or trailing commas in SQL?
Both are valid. Leading commas make it easy to comment out or add the last column and give cleaner diffs; trailing commas read more naturally. Pick one and let the formatter enforce it.