SQL formatting standards

Viewed 48774

In my last job, we worked on a very database-heavy application, and I developed some formatting standards so that we would all write SQL with a common layout. We also developed coding standards, but these are more platform-specific so I'll not go into them here.

I'm interested to know what other people use for SQL formatting standards. Unlike most other coding environments, I haven't found much of a consensus online for them.

To cover the main query types:

select
    ST.ColumnName1,
    JT.ColumnName2,
    SJT.ColumnName3
from 
    SourceTable ST
inner join JoinTable JT
    on JT.SourceTableID = ST.SourceTableID
inner join SecondJoinTable SJT
    on ST.SourceTableID = SJT.SourceTableID
    and JT.Column3 = SJT.Column4
where
    ST.SourceTableID = X
    and JT.ColumnName3 = Y

There was some disagreement about line feeds after select, from and where. The intention on the select line is to allow other operators such as "top X" without altering the layout. Following on from that, simply keeping a consistent line feed after the key query elements seemed to result in a good level of readability.

Dropping the linefeed after the from and where would be an understandable revision. However, in queries such as the update below, we see that the line feed after the where gives us good column alignment. Similarly, a linefeed after group by or order by keeps our column layouts clear and easy to read.

update
    TargetTable
set
    ColumnName1 = @value,
    ColumnName2 = @value2
where
    Condition1 = @test

Finally, an insert:

insert into TargetTable (
    ColumnName1,
    ColumnName2,
    ColumnName3
) values (
    @value1,
    @value2,
    @value3
)

For the most part, these don't deviate that far from the way MS SQL Server Managements Studio / query analyser write out SQL, however they do differ.

I look forward to seeing whether there is any consensus in the Stack Overflow community on this topic. I'm constantly amazed how many developers can follow standard formatting for other languages and suddenly go so random when hitting SQL.

28 Answers

I'm late to the party, but I'll just add my preferred formatting style, which I must've learned from books and manuals: it's compact. Here's the sample SELECT statement:

SELECT  st.column_name_1, jt.column_name_2,
        sjt.column_name_3
FROM    source_table AS st
        INNER JOIN join_table AS jt USING (source_table_id)
        INNER JOIN second_join_table AS sjt ON st.source_table_id = sjt.source_table_id
                AND jt.column_3 = sjt.column_4
WHERE   st.source_table_id = X
AND     jt.column_name_3 = Y

In short: 8-space indentation, keywords in caps (although SO colours them better when in lowercase), no camelcase (pointless on Oracle), and line wraps when needed.

The UPDATE:

UPDATE  target_table
SET     column_name_1 = @value,
        column_name_2 = @value2
WHERE   condition_1 = @test

And the INSERT:

INSERT  INTO target_table (column_name_1, column_name_2,
                column_name_3)
VALUES  (@value1, @value2, @value3)

Now, let me be the first to admit that this style has it's problems. The 8-space indent means that ORDER BY and GROUP BY either misalign the indent, or split the word BY off by itself. It would also be more natural to indent the entire predicate of the WHERE clause, but I usually align following AND and OR operators at the left margin. Indenting after wrapped INNER JOIN lines is also somewhat arbitrary.

But for whatever reason, I still find it easier to read than the alternatives.

I'll finish with one of my more complex creations of late using this formatting style. Pretty much everything you'd encounter in a SELECT statement shows up in this one. (It's also been altered to disguise its origins, and I may have introduced errors in so doing.)

SELECT  term, student_id,
        CASE
            WHEN ((ft_credits > 0 AND credits >= ft_credits) OR (ft_hours_per_week > 3 AND hours_per_week >= ft_hours_per_week)) THEN 'F'
            ELSE 'P'
        END AS status
FROM    (
        SELECT  term, student_id,
                pm.credits AS ft_credits, pm.hours AS ft_hours_per_week,
                SUM(credits) AS credits, SUM(hours_per_week) AS hours_per_week
        FROM    (
                SELECT  e.term, e.student_id, NVL(o.credits, 0) credits,
                        CASE
                            WHEN NVL(o.weeks, 0) > 5 THEN (NVL(o.lect_hours, 0) + NVL(o.lab_hours, 0) + NVL(o.ext_hours, 0)) / NVL(o.weeks, 0)
                            ELSE 0
                        END AS hours_per_week
                FROM    enrollment AS e
                        INNER JOIN offering AS o USING (term, offering_id)
                        INNER JOIN program_enrollment AS pe ON e.student_id = pe.student_id AND e.term = pe.term AND e.offering_id = pe.offering_id
                WHERE   e.registration_code NOT IN ('A7', 'D0', 'WL')
                )
                INNER JOIN student_history AS sh USING (student_id)
                INNER JOIN program_major AS pm ON sh.major_code_1 = pm._major_code AND sh.division_code_1 = pm.division_code
        WHERE   sh.eff_term = (
                        SELECT  MAX(eff_term)
                        FROM    student_history AS shi
                        WHERE   sh.student_id = shi.student_id
                        AND     shi.eff_term <= term)
        GROUP   BY term, student_id, pm.credits, pm.hours
        )
ORDER   BY term, student_id

This abomination calculates whether a student is full-time or part-time in a given term. Regardless of the style, this one's hard to read.

I am of the opinion that so long as you can read the source code easily, the formatting is secondary. So long as this objective is achieved, there are a number of good layout styles that can be adopted.

The only other aspect that is important to me is that whatever coding layout/style you choose to adopt in your shop, ensure that it is consistently used by all coders.

Just for your reference, here is how I would present the example you provided, just my layout preference. Of particular note, the ON clause is on the same line as the join, only the primary join condition is listed in the join (i.e. the key match) and other conditions are moved to the where clause.

select
    ST.ColumnName1,
    JT.ColumnName2,
    SJT.ColumnName3
from 
    SourceTable ST
inner join JoinTable JT on 
    JT.SourceTableID = ST.SourceTableID
inner join SecondJoinTable SJT on 
    ST.SourceTableID = SJT.SourceTableID
where
        ST.SourceTableID = X
    and JT.ColumnName3 = Y
    and JT.Column3 = SJT.Column4

One tip, get yourself a copy of SQL Prompt from Red Gate. You can customise the tool to use your desired layout preferences, and then the coders in your shop can all use it to ensure the same coding standards are being adopted by everyone.

Nice. As a Python programmer, here are my preferences:

Newlines after select, from and where only when it is needed for readability.

When code can be more compact and equally readable, I usually prefer the more compact form. Being able to fit more code in one screenful improves productivity.

select ST.ColumnName1, JT.ColumnName2, SJT.ColumnName3
from SourceTable ST
inner join JoinTable JT
    on JT.SourceTableID = ST.SourceTableID
inner join SecondJoinTable SJT
    on ST.SourceTableID = SJT.SourceTableID
    and JT.Column3 = SJT.Column4
where ST.SourceTableID = X and JT.ColumnName3 = Y

Ultimately, this will be a judgment call that will be made during code review.

For insert, I would place the parenthesis differently:

insert into TargetTable (
    ColumnName1,
    ColumnName2,
    ColumnName3)
values (
    @value1,
    @value2,
    @value3)

The reasoning for this formatting is that if SQL used indentation for block structure (like Python), the parenthesis would not be needed. So, if indentation is used anyway, then parenthesis should have the minimum effect on the layout. This is achieved by placing them at the end of the lines.

I tend to use a layout similar to yours, although I even go a few steps further, e.g.:

select
        ST.ColumnName1
    ,   JT.ColumnName2
    ,   SJT.ColumnName3
from
                SourceTable     ST

    inner join  JoinTable       JT
        on  JT.SourceTableID    =   ST.SourceTableID

    inner join  SecondJoinTable SJT
        on  ST.SourceTableID    =   SJT.SourceTableID

where
        ST.SourceTableID    =   X
    and JT.ColumnName3      =   Y
    and JT.Column3          =   SJT.Column4

Perhaps it looks a little over the top at first, but IMHO the use of tabulation in this way gives the cleanest, most systematic layout given the declarative nature of SQL.

You'll probably end up with all sorts of answers here. In the end, it's down to personal or team-agreed preferences.

I use a format similar to yours except that I put the ON keyword on the same line as the join and I put AND and OR operators at the end of lines so that all of my join/selection criteria line up nicely.

While my style is similar to John Sansom's, I disagree about putting join criteria in the WHERE clause. I think that it should be with the joined table so that it's organized and easy to find.

I also tend to put parentheses on new lines, aligned with the line above it and then indenting on the next line, although for short statements, I may just keep the parentheses on the original line. For example:

SELECT
     my_column
FROM
     My_Table
WHERE
     my_id IN
     (
          SELECT
               my_id
          FROM
               Some_Other_Table
          WHERE
               some_other_column IN (1, 4, 7)
     )

For CASE statements, I give a new line and indentation for each WHEN and ELSE, and I align the END back to the CASE:

CASE
     WHEN my_column = 1 THEN 'one'
     WHEN my_column = 2 THEN 'two'
     WHEN my_column = 3 THEN 'three'
     WHEN my_column = 4 THEN 'four'
     ELSE 'who knows'
END

Nobody's done common table expressions (CTEs) yet. Below incorporates it along with some other styles I use:

declare @tableVariable table (
    colA1 int,
    colA2 int,
    colB1 int,
    colB2 nvarchar(255),
    colB3 nvarchar(255),
    colB4 int,
    colB5 bit,
    computed int
);

with

    getSomeData as (

        select        st.colA1, sot.colA2
        from          someTable st
        inner join    someOtherTable sot on st.key = sot.key

    ),

    getSomeOtherData as (

        select        colB1, 
                      colB2, 
                      colB3,
                      colB4,
                      colB5,
                      computed =    case 
                                    when colB5 = 1 then 'here'
                                    when colB5 = 2 then 'there'
                                    end
        from          aThirdTable tt
        inner hash 
         join         aFourthTable ft
                      on tt.key1 = ft.key2
                      and tt.key2 = ft.key2
                      and tt.key3 = ft.key3

    )

    insert      @tableVariable (
                    colA1, colA2, colA2, 
                    colB1, colB2, colB3, colB4, colB5, 
                    computed 
                )
    select      colA1, colA2, 
                colB1, colB2, colB3, colB4, colB5, 
                computed 
    from        getSomeData data1
    join        getSomeOtherData data2

A few points on the CTE format:

  • In my CTEs "with" is on a seperate line, and everything else in the cte is indented.
  • My CTE names are long and descriptive. CTE's can get complex and descriptive names are very helpful.
  • For some reason, I find myself preferring verbs for CTE names. Makes it seem more lively.
  • Similar style with the parentheses as Javascript does with its braces. It's also how I do the braces in C#.

This simulates:

func getSomeData() {

    select        st.colA1, sot.colA2
    from          someTable st
    inner join    someOtherTable sot on st.key = sot.key

}

A few points besides the CTE format:

  • Two tabs after "select" and other keywords. That leaves enough room for "inner join", "group by", etc. You can see one example above where that's not true. But "inner hash join" SHOULD look ugly. Nevertheless, on this point I'm probably going to experiment with some of the styles above in the future.
  • Keywords are lowercase. Their colorization by the IDE and their special indentation status highlight them enough. I reserve uppercase for other things I want to emphasize based on local (business) logic.
  • If there are few columns, I put them on one row (getSomeData). If there are a few more, I verticalize them (getSomeOtherData). If there is too much verticalization in one unit, I horizontalize some columns into the same line grouped by locally defined logic (the final insert-select segment). For instance, I would put school-level information on one line, student-level on another, etc.
  • Especially when verticalizing, I prefer sql server's "varname = colname + something syntax" to "colname + something as varname".
  • Double the last point if I'm dealing with a case statement.
  • If a certain logic lends itself to a 'matrix' style, I will deal with the typing consequences. That's sort of what's going on with the case statement, where the 'whens' and 'then's are aligned.

I find that I am more settled on my CTE style than other areas. Haven't experimented with the styles more similar to that posed in the question. Probably will do someday and see how I like it. I'm probably cursed to be in an environment where it's a choice, though it's a fun curse to have.

If I am making changes to already written T-SQL, then I follow the already used convention (if there is one).

If I am writing from scratch or there is no convention, then I tend to follow your convention given in the question, except I prefer to use capital letters for keywords (just a personal preference for readability).

I think with SQL formatting as with other code format conventions, the important point is to have a convention, not what that convention is (within the realms of common sense of course!)

Yeah I can see the value of laying out your sql in some rigourously defined way, but surely the naming convention and your intent are far more important. Like 10 times more important.

Based on that my pet hates are tables prefixed by tbl, and stored procedures prefixed by sp - we know they're tables and SPs. Naming of DB objects is far more important than how many spaces there are

Just my $0.02 worths

This is the format that I use. Please comment if it can be make better.

CREATE PROCEDURE [dbo].[USP_GetAllPostBookmarksByUserId]
    @id INT,
    @startIndex INT,
    @endIndex INT
AS
BEGIN

    SET NOCOUNT ON

    SELECT      *
    FROM
            (   SELECT      ROW_NUMBER() OVER ( ORDER BY P.created_date ) AS row_num, P.post_id, P.title, P.points, p.estimated_read_time, P.view_count, COUNT(1) AS "total_attempts" -- todo
                FROM        [dbo].[BOOKMARKED] B
                INNER JOIN  [dbo].[POST] P
                ON          B.entity_id = P.post_id
                INNER JOIN  [dbo].[ATTEMPTED] A
                ON          A.entity_id = P.post_id
                WHERE       B.user_id = 1 AND P.is_active = 1
                GROUP BY    P.post_id, P.title, P.points, p.estimated_read_time, P.view_count
            )   AS PaginatedResult
    WHERE       row_num >= @startIndex
    AND         row_num < @endIndex
    ORDER BY    row_num

END

My answer will be similar to the accepted answer by John Sansom answered Feb 6 '09 at 11:05. However, I will demonstrate some formatting options using SQLInForm plugin in NOTEPAD++, as opposed to his answer with SQL Prompt from Red Gate.

The SQLInForm plugin has 5 different profiles you can setup. Within the profile there are lots of settings available in both the FREE and PAID versions. An exhaustive list is below and you can see their plugin-help-general-options page online.

Instead of rambling about my preferences I considered it would be useful to present the SQLInForm options available. Some of my preferences are also noted below. At the end of my post is the formatted SQL Code used in the original post (original VS format1 VS format2).

Reading through other answers here-- I seem to be in the minority on a couple things. I like leading commas (Short Video Here)-- IMO, It is much easier to read when a new field is being selected. And also I like my Column1 with linebreak and not next to the SELECT.


Here is an overview with some of my preferences notes, considering a SELECT Statement. I would add screenshots of all 13 sections; But that is a lot of screenshots and I would just encourage you to the the free edition-- take some screenshots, and test the format controls. I will be testing out Pro edition soon; But based on the options it looks like it will be really helpful and for only $20.

SQLInForm Notepadd++: Options & Preferences

1. General (free)

DB: Any SQL, DB2/UDB, Oracle, MSAccess, SQL Server, Sybase, MYSQL, PostgreSQL, Informix, Teradata, Netezza SQL

[Smart Indent]= FALSE

2. Colors (free)

3. Keywords (PRO)

[Upper/LowerCase]> Keywords

4. Linebreaks> Lists (free)

[Before Comma]=TRUE 5

[Move comma 2 cols to the left]= FALSE

5. Linebreaks>Select (PRO)

[JOIN> After JOIN]= FALSE

[JOIN> Before ON]= FALSE

(no change)--> [JOIN> Indent JOIN]; [JOIN> After ON]

6. Linebreaks> Ins/Upd/Del (PRO)

7. Linebreaks> Conditions (PRO)

CASE Statement--> [WHEN], [THEN], [ELSE] … certainly want to play with these settings and pick a good one

8. Alignment (PRO)

(no change)--> [JOIN> Indent JOIN]; [JOIN> After ON]

9. White Spaces (PRO)

(change?) Blank Lines [Remove All]=TRUE; [Keep All]; [Keep One]

10. Comments (PRO)

(change?) Line & Block--> [Linebreak Before/ After Block Comments]=TRUE; [Change Line Comments into Block]; [Block into Line]

11. Stored Proc (PRO)

12. Advanced (PRO)

(Could be useful) Extract SQL from Program Code--> [ExtractSQL]

13. License


SQL Code

The original query format.

select
    ST.ColumnName1,
    JT.ColumnName2,
    SJT.ColumnName3
from 
    SourceTable ST
inner join JoinTable JT
    on JT.SourceTableID = ST.SourceTableID
inner join SecondJoinTable SJT
    on ST.SourceTableID = SJT.SourceTableID
    and JT.Column3 = SJT.Column4
where
    ST.SourceTableID = X
    and JT.ColumnName3 = Y

CONVERSION PREFERRED FORMAT (option #1: JOIN no linebreak)

SELECT
    ST.ColumnName1
    , JT.ColumnName2
    , SJT.ColumnName3
FROM
    SourceTable ST
    inner join JoinTable JT 
        on JT.SourceTableID = ST.SourceTableID
    inner join SecondJoinTable SJT
        on ST.SourceTableID = SJT.SourceTableID
        and JT.Column3      = SJT.Column4
WHERE
    ST.SourceTableID   = X
    and JT.ColumnName3 = Y

CONVERSION PREFERRED FORMAT (option #2: JOIN with linebreak)

SELECT  
    ST.ColumnName1
    , JT.ColumnName2
    , SJT.ColumnName3
FROM
    SourceTable ST
    inner join
        JoinTable JT
        on JT.SourceTableID = ST.SourceTableID
    inner join
        SecondJoinTable SJT
        on ST.SourceTableID = SJT.SourceTableID
        and JT.Column3      = SJT.Column4
WHERE
    ST.SourceTableID   = X
    and JT.ColumnName3 = Y

Hope this helps.

A hundred answers here already, but after much toing and froing over the years, this is what I've settled on:

SELECT      ST.ColumnName1
          , JT.ColumnName2
          , SJT.ColumnName3

FROM        SourceTable       ST
JOIN        JoinTable         JT  ON  JT.SourceTableID  =  ST.SourceTableID
JOIN        SecondJoinTable  SJT  ON  ST.SourceTableID  =  SJT.SourceTableID
                                  AND JT.Column3        =  SJT.Column4

WHERE       ST.SourceTableID  =  X
AND         JT.ColumnName3    =  Y

I know this can make for messy diffs as one extra table could cause me to re-indent many lines of code, but for my ease of reading I like it.

This is my personal SQL style guide. It is based on a couple of others, but has a few main stylistic features - lowercase keywords, no extraneous keywords (e.g. outer, inner, asc), and a "river".

Example SQL looks like this:

-- basic select example
select p.Name as ProductName
     , p.ProductNumber
     , pm.Name as ProductModelName
     , p.Color
     , p.ListPrice
  from Production.Product as p
  join Production.ProductModel as pm
    on p.ProductModelID = pm.ProductModelID
 where p.Color in ('Blue', 'Red')
   and p.ListPrice < 800.00
   and pm.Name like '%frame%'
 order by p.Name

-- basic insert example
insert into Sales.Currency (
    CurrencyCode
    ,Name
    ,ModifiedDate
)
values (
    'XBT'
    ,'Bitcoin'
    ,getutcdate()
)

-- basic update example
update p
   set p.ListPrice = p.ListPrice * 1.05
     , p.ModifiedDate = getutcdate()
  from Production.Product p
 where p.SellEndDate is null
   and p.SellStartDate is not null

-- basic delete example
delete cc
  from Sales.CreditCard cc
 where cc.ExpYear < '2003'
   and cc.ModifiedDate < dateadd(year, -1, getutcdate())
Related