SQL formatting guidelines
Well formatted code is easier to read and to maintain, resulting in a higher overall quality. There are many good ways to format SQL code. This document provides some guidelines we use at Thinkwise to write legible and structured SQL code.
The guidelines are structured per statement. All guidelines are clarified with an example.
General guidelines
- Use 4 spaces instead of tabs.
- Indent using a multiple of 4 spaces.
- Align opening and closing keywords (
beginandend,caseandend, etc.). - Do not use empty lines inside a single statement.
- Place commas in front of the column names.
SELECT
- Left align the
select,from,where,order by,havingandgroup bykeywords. - Place the select list under the
selectkeyword and indent using 4 spaces. - Provide an alias for all columns without a name (constants, functions, composite columns), using the
askeyword. - Provide an alias for all tables, consisting of the first letter of every subname, without using the
askeyword. If this is not sufficient, add a number or choose another meaningful alias. - Place composite or calculated columns on one line, unless the the line is too long.
Example SELECT
select
si.sales_invoice_id
,si.customer_id
,si.invoice_date
,getdate() as due_date
,si.amount_excl_vat
,si.amount_incl_vat
,1 as invoice_status
from sales_invoice si
select
concat(e.last_name, ' ', e.first_name) as name
,e.email
from employee e
Some people prefer to provide an alias for all columns. Be sure to left align the aliases if you choose to do so.
ORDER BY and GROUP BY
- Place the order by or group by list under the
order byorgroup bykeyword and indent using 4 spaces.
Example ORDER BY
select
si.sales_invoice_id
,si.customer_id
,si.invoice_date
,getdate() as due_date
,si.amount_excl_vat
,si.amount_incl_vat
,1 as invoice_status
from sales_invoice si
order by
si.customer_id
,si.invoice_date
,si.invoice_status
Example GROUP BY
select
p.project_id
,h.date
,avg(h.number_of_hours) as avg_number_of_hours
from project p
join hour h
on h.project_id = p.project_id
group by
p.project_id
,p.description
,h.date
WHERE and HAVING
- Right align the top level
andkeywords with thewhereorhavingkeyword. - Place
andkeywords in front of the condition. - Place
orkeywords on a separate line, left aligned with the previous line. - Always use parentheses around
orconditions. - Indent conditions inside parentheses, using a multiple of 4 spaces.
- Left align the closing parentheses
)with the condition (e.g.and) of the opening parentheses. - Align comparison operators (
=,<, etc.) for conditions of the same level.
Example WHERE
select
si.sales_invoice_id
,si.customer_id
,si.invoice_date
,getdate() as due_date
,si.amount_excl_vat
,si.amount_incl_vat
,1 as invoice_status
from sales_invoice si
where si.invoice_date = '2019-1-1'
and si.customer_id = 15
and (
si.invoice_status = 1
or
si.amount_excl_vat > 10.000
)
Example HAVING
select
p.project_id
,h.date
,avg(h.number_of_hours) as avg_number_of_hours
,max(h.number_of_hours) as max_number_of_hours
from project p
join hour h
on h.project_id = p.project_id
group by
p.project_id
,p.description
,h.date
having avg(h.number_of_hours) > 5
and max(h.number_of_hours) < 12
CASE expressions
- Align the
caseand theendkeywords. - Indent the
whenandelseexpressions, using a multiple of 4 spaces. - Place the
thenexpression on the same line as thewhen, unless the line is too long. - When the line is too long, place the
thenkeyword on a new line and indent using 4 spaces.
Example simple CASE
select
so.sales_order_id
,case so.order_status
when 0 then 'not_approved'
when 1 then 'approved'
when 2 then 'sent'
else 'delivered'
end as order_status
,so.customer_id
from sales_order so
Example searched CASE
select
so.sales_order_id
,case
when so.order_status = 0 or so.order_status is null
then 'not_approved'
when so.order_status = 1 then 'approved'
when so.order_status = 2 then 'sent'
else 'delivered'
end as order_status
,so.customer_id
from sales_order so
JOIN
- Prevent the use of right joins.
- Don't use
innerfor regular (inner) joins orouterfor left joins. - Left align the
join,left joinandcross joinkeywords. - Right align the
onandandkeywords with thejoinkeyword. - Align comparison operators (
=,<, etc.) for join conditions. - Place the columns of the joined table on the left side of the comparison.
Example JOIN
select
p.description
,sp.name
,h.number_of_hours
from project p
join sub_project sp
on sp.project_id = p.project_id
join hour h
on h.project_id = sp.project_id
and h.sub_project_id = sp.sub_project_id
Example LEFT JOIN
select
p.description
,sp.name
from project p
left join sub_project sp
on sp.project_id = p.project_id
UNION
- Left align the
unionorunion allkeyword. - Place empty lines before and after the
unionkeyword. - Use comments to describe the select statements.
Example UNION ALL
--Approved sales invoices
select
si.sales_invoice_id
,'Approved' as status
from sales_invoice si
where si.invoice_status = 1 --Approved
union all
--Not approved sales invoices
select
si.sales_invoice_id
,'Not Approved' as status
from sales_invoice si
where si.invoice_status = 0 --Not approved
Functions
- Place function calls on a single line, unless the line is too long.
- When the line is too long, place the parameters on a new line and indent using 4 spaces
- Left align the closing parentheses
)with the function name (e.g.datediff).
Example FUNCTION
select
si.sales_invoice_id
,si.invoice_date
,si.due_date
,datediff(day, si.invoice_date, si.due_date) as number_of_days
from sales_invoice si
Example FUNCTION with many parameters
select
si.sales_invoice_id
,si.invoice_date
,si.due_date
,datediff(
day
,si.invoice_date
,si.due_date
) as number_of_days
,si.invoice_status
from sales_invoice si
Subqueries
- Consider using
applyinstead of subqueries to improve readability. Usecross applyfor regular (inner) joins andouter applyfor left joins. - Indent subqueries relative to the opening parenthesis or the
applykeyword, using a multiple of 4 spaces. - Align the closing parentheses
)with the opening parentheses(.
Example subquery in SELECT
๐ก Use OUTER APPLY instead
select
p.project_id
,(
select sum(h.number_of_hours)
from hour h
where h.project_id = p.project_id
) as number_of_hours
from project p
Alternative using OUTER APPLY
select
p.project_id
,s.number_of_hours
from project p
outer apply (
select sum(h.number_of_hours) as number_of_hours
from hour h
where h.project_id = p.project_id
) s
Example subquery in FROM
๐ก Use CROSS APPLY instead
select
p.project_id
,h.number_of_hours
from project p
join (
select
h.project_id
,sum(h.number_of_hours) as number_of_hours
from hour h
group by h.project_id
) h
on h.project_id = p.project_id
Alternative using CROSS APPLY
select
p.project_id
,s.number_of_hours
from project p
cross apply (
select sum(h.number_of_hours) as number_of_hours
from hour h
where h.project_id = p.project_id
) s
Example subquery in WHERE
Use CROSS or OUTER APPLY instead
select p.project_id as project_id
from project p
where p.finished = 0
and 100 >= (
select sum(h.number_of_hours) as number_of_hours
from hour h
where h.project_id = p.project_id
)
Example using CROSS APPLY
select p.project_id as project_id
from project p
cross apply (
select sum(h.number_of_hours) as number_of_hours
from hour h
where h.project_id = p.project_id
) s
where p.finished = 0
and s.number_of_hours < 100
IN and EXISTS
- Use
inwith constant values only andexistswith subqueries. - Indent subqueries relative to the
existskeyword, using a multiple of 4 spaces. - Left align the closing parentheses
)with theexistskeyword.
Example EXISTS
select p.description
from project p
where exists (
select 1
from sub_project sp
where sp.project_id = p.project_id
)
Example IN
select p.description
from project p
where p.status in (1, 2, 3) --new, open, closed
INSERT
- Don't use the
intokeyword. - Always use a column list.
- Place the column list under the
insertkeyword and indent using 4 spaces. - Left align the closing parentheses with the
insertkeyword. - Left align the
selectorvalueskeyword with theinsertkeyword.
Example
insert project (
customer_id
,description
,planned_start_date
,planned_end_date
,actual_start_date
,finished
,finished_on_date
)
select
p.customer_id
,p.description
,p.planned_start_date
,p.planned_end_date
,null as actual_start_date
,0 as finished
,null as finished_on_date
from project p
where p.project_id = 3
insert project (
customer_id
,description
)
values (
(1, 'project 1')
,(2, 'project 2')
,(3, 'project 3')
)
UPDATE
- Use a from-clause with joins instead of subqueries.
- Always use the alias of the table to update in the
updatestatement. - Left align the
setkeyword with theupdatekeyword. - Place the column list after the
setkeyword and indent using 4 spaces. - Align the assignment operators
=of the column list.
Example UPDATE
update sp
set sp.finished = p.finished
,finished_on_date = p.finished_on_date
from sub_project sp
join project p
on p.project_id = sp.project_id
where p.finished = 1
DELETE
- Use a from-clause with joins instead of subqueries.
- Always use the alias of the table in the
deletestatement.
Example DELETE
delete sp
from sub_project sp
join project p
on p.project_id = sp.project_id
where p.finished = 1
DECLARE variables
- Declare all variables at the top of the code template.
- Place the variable list under the
declarekeyword and indent using 4 spaces. - Place commas in front of the variable names.
- Left align the data types for all variables.
Example DECLARE
declare
@project_id project_id
,@project_vrs_id project_vrs_id
,@tab_id tab_id
IF and WHILE
- Always use
beginandendin an if or while statement. - Left align the
if,while,beginandendkeywords. - Don't use empty lines after the
beginand before theendkeywords. - Do use empty lines to separate statements within
beginandendblocks. - Left align top level
andkeywords with the first condition. - Place
andkeywords in front of the condition. - Place
orkeywords on a separate line, left aligned with the previous line. - Always use parentheses around
orconditions. - Indent conditions inside parentheses, using a multiple of 4 spaces.
- Align the closing parentheses
)with the opening parentheses(. - Align comparison operators (
=,<, etc.) for conditions of the same level.
Example IF
if @project_id = 1
and @project_vrs_id = 'DB'
and (
@project_status = 3
or
@project_status = 5
)
begin
set @project_vrs_id = 'DBA'
set @project_status = 6
end
Example IF with nested parentheses
if (
(
@project_id = 1
and @project_vrs_id = 'DB'
)
or
@project_status = 3
)
begin
set @project_vrs_id = 'DBA'
end
Example WHILE
while @status = 3
and @counter >= 1
begin
set @counter = @counter + 1
end
Table variables and temporary tables
- Place the column list under the
selectkeyword and indent using 4 spaces. - Place commas in front of the column names.
- Left align the data types for all variables.
Example table variable
For table variables, use domains as much as possible.
declare @project table (
project_id project_id
,description description
)
Example temporary table
create table #project (
project_id int
,description varchar(200)
)
drop table #project
Common table expressions (CTEs)
- Left align the
with,asandselect,updateorinsertkeywords. - Place the column list under the
withkeyword and indent using 4 spaces. - Place commas in front of the column names.
Example CTE
;with sales_invoice_vat (
sales_invoice_id
,vat_percentage
)
as (
select
sales_invoice_id
,100 * ((amount_incl_vat - amount_excl_vat)/amount_excl_vat) as vat_percentage
from sales_invoice
where amount_excl_vat <> 0
)
select
si.sales_invoice_id,
siv.vat_percentage
from sales_invoice si
left join sales_invoice_vat siv
on siv.sales_invoice_id = si.sales_invoice_id
CURSOR
- Place the cursor parameters on the same line as the
declarekeyword. - Left align the
declareandselectkeywords. - Place all variables on the same line as the
fetchkeyword.
Example CURSOR
declare
@country_id id
,@country_name name
declare countries cursor local static read_only forward_only for
select
c.country_id
,c.name
from country c
order by c.name
open countries
fetch next from countries into @country_id, @country_name
while @@fetch_status = 0
begin
print @country_name
print @country_id
fetch next from countries into @country_id, @country_name
end
close countries
deallocate countries
Transactions
- Left align the
begin tran,commit tranandrollback trankeywords. - Left align the code within the transaction.
- Don't name the transaction unless there are nested transactions.
Example transaction
begin tran
insert project (
customer_id
,description
,planned_start_date
,planned_end_date
,actual_start_date
,finished
,finished_on_date
)
select
p.customer_id
,p.description
,p.planned_start_date
,p.planned_end_date
,null as actual_start_date
,0 as finished
,null as finished_on_date
from project p
where p.project_id = 3
commit tran
TRY CATCH
- Left align the
begin try,end try,begin catchandend catchkeywords. - Indent the code within the try and catch, using a multiple of 4 spaces.
Example TRY CATCH with transaction
begin try
begin tran
insert project (
customer_id
,description
,planned_start_date
,planned_end_date
,actual_start_date
,finished
,finished_on_date
)
select
p.customer_id
,p.description
,p.planned_start_date
,p.planned_end_date
,null as actual_start_date
,0 as finished
,null as finished_on_date
from project p
where p.project_id = 3
commit tran
end try
begin catch
rollback tran
throw
end catch
Procedure calls
- Place the parameters on the same line unless there are many parameters.
- When there are many parameters, place the parameters on a new line and indent using 4 spaces.
- Place commas in front of the parameters.
- Align the
outputkeywords.
Example procedure call
exec task_kopieer_project @project_id
Example with output parameters
exec task_kopieer_project
@project_id
,@klant_id
,@datum
,@verwachte_kosten output
,@verwachte_einddatum output
Comments
- Use
--for single line comments and/* ... */for multiline comments.To quickly comment or uncomment a block of code for debugging purposes, select the code and use your editors' shortcut.
For SQL Server Management Studio and Azure Data Studio, this isCtrl+K,CandCtrl+K,U. - Don't describe what code used to do or what has changed.
- Don't leave commented-out code in templates.
Example comments
/*
This is an example
of multiline comment
*/
-- Update today if it is different from the current date
if exists (
select 1
from settings i
where i.today <> cast(getdate() as date)
)
begin
update settings
set today = cast(getdate() as date)
end
