T-SQL carries more procedural syntax than any other mainstream dialect, and a good deal of it is not really SQL at all — GO is a client directive, table variables are declared with the same @ that marks a parameter, and query hints ride along at the end of a statement in parentheses. Formatting T-SQL well is mostly a matter of recognising which of these are statements and which are decorations.
The query below, loaded into the editor above, exercises the parts that most often go wrong: bracketed multi-part names, an OUTER APPLY correlated to the outer row, and a windowed ROW_NUMBER with its own PARTITION BY and ORDER BY.
Before and after
This is the query loaded in the editor above. On the left is what you paste; on the right is what the formatter returns with the default options for this dialect.
with ranked as (select [c].[CustomerID], [c].[Name], o.[OrderID], o.[Total], row_number() over (partition by [c].[CustomerID] order by o.[OrderDate] desc) as rn from [dbo].[Customers] c outer apply (select top (3) * from [dbo].[Orders] o where o.[CustomerID] = c.[CustomerID] order by o.[Total] desc) o where [c].[IsActive] = 1) select [CustomerID], [Name], [Total] from ranked where rn = 1 order by [Total] desc;WITH
ranked AS (
SELECT
[c].[CustomerID],
[c].[Name],
o.[OrderID],
o.[Total],
ROW_NUMBER() OVER (
PARTITION BY
[c].[CustomerID]
ORDER BY
o.[OrderDate] DESC
) AS rn
FROM
[dbo].[Customers] c
OUTER APPLY (
SELECT
TOP (3) *
FROM
[dbo].[Orders] o
WHERE
o.[CustomerID] = c.[CustomerID]
ORDER BY
o.[Total] DESC
) o
WHERE
[c].[IsActive] = 1
)
SELECT
[CustomerID],
[Name],
[Total]
FROM
ranked
WHERE
rn = 1
ORDER BY
[Total] DESC;What is specific to SQL Server (T-SQL)
Bracketed identifiers and four-part names
SQL Server quotes identifiers with square brackets, and a fully qualified name can carry four parts: server.database.schema.object. Brackets are preserved and the dots between them are not treated as operators, so [dbo].[Customers] stays intact.
Double quotes also work as identifier delimiters when QUOTED_IDENTIFIER is ON, which is the default for most drivers. Both spellings are accepted.
CROSS APPLY and OUTER APPLY
APPLY is the T-SQL lateral join: the right-hand subquery is evaluated once per row of the left-hand table and can reference its columns. CROSS APPLY drops outer rows that produce nothing, OUTER APPLY keeps them with NULLs — the same relationship as INNER and LEFT JOIN.
The subquery is expanded as a nested block, which makes the correlation visible. In the example, o.[CustomerID] = c.[CustomerID] inside the APPLY is what ties it to the outer row.
Window frames
An OVER clause can contain PARTITION BY, ORDER BY and a frame specification, which makes it a clause nested inside a select item. Each part is placed on its own line rather than run together, because a mis-read PARTITION BY is one of the easier ways to get a wrong answer that still looks plausible.
Frame clauses such as ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW are kept on the line with their keywords.
GO is a batch separator, not a statement
GO is understood by SSMS, Azure Data Studio and sqlcmd, not by the SQL Server engine — the client splits the script on it and sends each batch separately. Scripts containing GO are handled correctly: the batches are formatted independently and the separator is kept on its own line.
SELECT
1
FROM
t;
GO
SELECT
2
FROM
u;MERGE
MERGE combines insert, update and delete against a target using a source, with WHEN MATCHED, WHEN NOT MATCHED BY TARGET and WHEN NOT MATCHED BY SOURCE branches. Each branch starts on its own line so the three cases can be read apart:
MERGE INTO
target t using source s ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET
t.v = s.v
WHEN NOT MATCHED BY TARGET THEN
INSERT
(id, v)
VALUES
(s.id, s.v);The leading semicolon before WITH
Scripts often start a CTE with ;WITH rather than WITH. The semicolon terminates whatever came before, because WITH is ambiguous — it also introduces table hints — and SQL Server requires the preceding statement to be terminated when a CTE follows.
The habit is harmless and the formatter accepts it, but terminating every statement with a semicolon makes it unnecessary, which is the better fix.
Known limitations
No formatter handles every corner of a dialect. These are the cases where this one produces output you may want to correct by hand.
- TOP (n) WITH TIES is not parsed correctly: WITH is read as the start of a common table expression, and the clause is broken across lines as TOP (n) / WITH / ties. The example on this page uses TOP (3) without WITH TIES for that reason. If you need WITH TIES, format the query and then repair that one clause by hand.
- In a MERGE statement the USING keyword is left in the case you typed it rather than uppercased with the other keywords.
Conventions worth adopting
PascalCase schemas, Preserve identifier case
SQL Server schemas are conventionally PascalCase — CustomerID, OrderDate — and SQL Server compares identifiers using the database collation, which is usually case-insensitive. Preserve is still the right default: it keeps your names readable, and it avoids surprises on the minority of databases running a case-sensitive collation.
Terminate statements
Microsoft has documented the omission of the statement terminator as deprecated for several releases. Ending every statement with a semicolon removes the need for the leading-semicolon trick, and lets the formatter place blank lines between statements reliably.
SQL Server (T-SQL) formatting FAQ
Can I paste a script with GO separators?
Yes. The batches are formatted independently and each GO is kept on its own line. GO is a client directive rather than T-SQL, so it is passed through rather than parsed as a statement.
Why does TOP (3) WITH TIES come out broken?
The parser reads WITH as the beginning of a common table expression. It is a known limitation, listed above. Everything else in the query formats normally, so the usual workaround is to fix that single clause afterwards.
Does it format stored procedure bodies?
The statements inside a procedure are formatted as ordinary T-SQL. Control-of-flow constructs such as IF and WHILE are recognised, but the result is less polished than for a plain SELECT — procedural code is where any SQL formatter is weakest.
Is my T-SQL sent to a server?
No. Everything runs in your browser, so queries containing internal schema or object names never leave your machine.
Other SQL dialects
Not sure which one you need, or working with more than one? The general SQL formatter lets you switch dialects without leaving the page.