SQL格式化标准

62
在我上一份工作中,我们开发了一个非常依赖数据库的应用程序,我制定了一些格式规范,以确保我们都使用相同的SQL布局进行编写。我们还制定了编码规范,但这些更具有平台特定性,所以我不会在这里详细介绍。
我想知道其他人用于SQL格式化标准的做法。与大多数其他编程环境不同的是,我在网上没有找到太多共识。
为了涵盖主要的查询类型:
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

selectfromwhere之后换行存在一些争议。选择在select行中这样做的意图是为了允许其他操作符,例如“top X”,而不会更改布局。在此基础上,仅在关键查询元素之后保持一致的换行看起来可以获得良好的可读性。

删除fromwhere后的换行可能是可以接受的修订。但是,在如下所示的update查询中,我们看到where后的换行使我们的列对齐很好。同样,在group byorder by之后换行将使我们的列布局清晰易读。

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

最后是一个 insert 操作:

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

大多数情况下,它们与 MS 的 SQL Server 管理工具 SQL Server Management Studio / 查询分析器编写 SQL 的方式没有太大差异,但是它们确实有所不同。

我期待在 Stack Overflow 社区中是否存在关于此主题的共识。我一直很惊讶于有多少开发人员可以遵循其他语言的标准格式,却在处理 SQL 时变得如此随意。


3
我倾向于在列名和值之前放置逗号,这样可以更容易地阅读。 - D3vtr0n
2
将逗号放在列名之前也使得注释掉代码行更容易,而不必担心删除上一行的逗号。 - NoAlias
3
我明白了,我经常看到这种情况。但是我想知道的是,如果在列表的第一行或最后一行注释掉代码,只有那时才会有区别,因此总体上并没有太大的影响,这个想法是否正确? - Timbo
不,你的想法并没有错,但当它是列表中的第一个或最后一个值时,你必须改变其他东西而不仅仅是添加注释,而如果在开头有逗号,那么只需要注释就足够了。 - claudekennilol
11
在开头放逗号并注释掉第一行时,您是否仍需删除第二个选择值上的逗号?这似乎只是将问题从最后一个值移动到第一个值... - Wouter
显示剩余3条评论
28个回答

24

回答晚了,但希望有所帮助。

我在大型开发团队中工作的经验是,你可以制定任何标准,但问题实际上在于执行这些标准或使开发者更容易实现它们。

作为开发人员,我们有时会创建一些可用的东西,然后说“稍后再格式化”,但是那个“稍后”从来没有到来。

最初,我们使用了 SQL Prompt(非常好),但后来转换到了ApexSQL Refactor,因为它是一个免费的工具。


ApexSQL在此提供了一个引人入胜的免费“Refactor”之旅:http://solutioncenter.apexsql.com/how-to-format-sql-like-a-pro-formatting-to-implicit-microsoft-standards-and-guidance/。 - BillVo

23

我来晚了,但我想分享一下我喜欢的代码格式,可能是从书本和手册中学习的:它很简洁。这是一个SELECT语句的示例:

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

简而言之:使用8个空格缩进,关键字大写(尽管 SO 在小写时会更好地着色),不使用驼峰格式(在 Oracle 上没有意义),需要时进行换行。

更新内容:

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

接下来是 INSERT 语句:

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

首先我要承认这种格式有它的问题。8个空格的缩进意味着ORDER BYGROUP BY要么错位缩进,要么单独分离出单词BY。对于WHERE子句的整个谓词来说更自然的方式是整体缩进,但我通常将ANDOR运算符的后续内容对齐到左边距。在换行的INNER JOIN行之后进行缩进也有些随意。

但不知为何,我仍然觉得这种格式比其他格式更易于阅读。

最后,我会展示我的一个比较复杂的创作,使用了这种格式。几乎所有你在SELECT语句中遇到的东西都会显示在其中。(同时已被修改以掩盖其来源,可能会引入错误。)

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

这个可怕的代码用于计算一个学生在某个学期是否为全职或兼职。无论其风格如何,这段代码都很难阅读。


20

我认为只要你能轻松阅读源代码,格式就不那么重要了。只要达到了这个目标,有很多好的布局风格可以采用。

对我来说,唯一重要的另一个方面是,无论你在你的团队中选择采用哪种编码布局/风格,请确保所有编码人员都始终如一地使用它。

仅供参考,以下是我会如何呈现您提供的示例,这是我的布局偏好。特别需要注意的是,ON子句与join位于同一行,只有主要的连接条件列在了join中(即键匹配),而其他条件则移到了where子句中。

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

一个建议,获取自Red GateSQL Prompt副本。你可以自定义该工具以使用你所需的布局首选项,然后你店里的编码人员都可以使用它,以确保每个人都采用相同的编码标准。


10
在联接语句中,只有主要的联接条件(即键匹配)会被列出,在where子句中移动其他条件。在内部联接中,这是可以的。但是在外部联接中,如果将on子句中需要的条件移到where子句中,意义就会改变。 - Shannon Severance

8
SELECT
    a.col1                  AS [Column1]
    ,b.col2                 AS [Column2]
    ,c.col1                 AS [Column3]
FROM
    Table1 a
    INNER JOIN Table2 b     ON b.Id = a.bId
    INNER JOIN Table3 c     ON c.Id = a.cId
WHERE
    a.col     = X
    AND b.col = Y

相比这里的许多示例,它使用了更多的行,但我认为它更容易理解,可以快速删除列/子句/表格。它有助于利用竖向监视器。


5
这种方法的缺点是,如果您将table2更改为schema.long_table_name,则必须重新对齐整个查询。此外,随着其他行上的空格增加,左右列之间匹配变得困难。 - Wouter

7

虽然有点晚,但我还是想发表一下意见。使用垂直对齐可能需要写更多的代码,但是当你习惯了之后,会发现其中蕴含着一些规律,使得代码更加易读。

SELECT ST.ColumnName1,
       JT.ColumnName2,
       SJT.ColumnName3,
       CASE WHEN condition1 = True 
             AND condition2 = True Then DoSomething
            Else DoSomethingElse
        END ColumnName4
  FROM SourceTable AS ST
 INNER
  JOIN JoinTable AS JT
    ON JT.SourceTableID = ST.SourceTableID
 INNER
  JOIN SecondJoinTable AS SJT
    ON ST.SourceTableID = SJT.SourceTableID
   AND JT.Column3 = SJT.Column4
  LEFT
  JOIN (SELECT Column5
          FROM Table4
       QUALIFY row_number() OVER
                 ( PARTITION BY pField1,
                                pField2
                       ORDER BY oField1
                 ) = 1
       ) AS subQry
    ON SJT.Column5 = subQry.Column5
 WHERE ST.SourceTableID = X
   AND JT.ColumnName3 = Y

我承认这样写可能看起来更易读...但也似乎很痛苦。 - Don Cheadle

6

我正在用C#编写一款开源的SQL格式化程序(目前仅支持SQL Server),因此我将上述查询语句输入该程序进行处理。

它采用了与OP类似的策略,即每个“部分”下面都有子元素缩进。在需要时,我会在部分之间添加空格以提高清晰度-当没有连接或最小的where条件时,不会添加这些空格。

结果:

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 ST.SourceTable2ID = SJT.SourceTable2ID

WHERE ST.SourceTableID = X
  AND JT.ColumnName3 = Y
  AND JT.Column3 = SJT.Column4

ORDER BY
    ST.ColumnName1

对我来说,这是所有建议中最易读的。我喜欢关键字靠右对齐,列/表名靠左对齐的方式。但是,不幸的是,手动使用此格式需要大量的“空格”“空格”“空格”才能正确对齐,因为“制表符”将文本对齐到左侧而不是右侧。 - Wouter
你好,这个在Github上吗?我正在寻找类似的东西,并且使用和你差不多的风格。 - bjpelcdev
@bjpelcdev 是的,它甚至包括一个SSMS插件,请参见-https://github.com/benlaan/sqlformat - Ben Laan
如果你想出一个格式化程序,如果你让它可配置,那么接受度会更高。仅仅通过阅读这个帖子中的所有评论/答案,就可以给你一个所需的想法。 - theking2

6
很好。作为一名Python程序员,这是我的偏好:
仅在需要提高可读性时,在selectfromwhere后添加换行符。
当代码可以更紧凑且同样易读时,我通常更喜欢更紧凑的形式。能够在一个屏幕上容纳更多的代码可以提高生产力。
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

最终,这将是在代码审查期间做出的判断。
对于insert,我会将括号放置在不同的位置:
insert into TargetTable (
    ColumnName1,
    ColumnName2,
    ColumnName3)
values (
    @value1,
    @value2,
    @value3)

这种格式的原因是,如果SQL使用缩进来表示块结构(就像Python一样),那么括号将不再需要。因此,如果仍然使用缩进,则括号应对布局产生最小的影响。这可以通过将它们放在行尾来实现。

我更喜欢这种写法:insert into TargetTabel( ColumnName1 , ColumnName2 , ColumnName3这样我就可以注释掉其中一个列而无需重新格式化了。insert into TargetTable( ColumnName1 --, ColumnName2 , ColumnName3 - JeffO

4
我建议采用以下样式,基于John的建议:
/*
<Query title>
<Describe the overall intent of the query>
<Development notes, or things to consider when using/interpreting the query>
*/
select
    ST.ColumnName1,
    JT.ColumnName2,
    SJT.ColumnName3
from 

    -- <Comment why this table is used, and why it's first in the list of joins>
    SourceTable ST

    -- <Comment why this join is made, and why it's an inner join>
    inner join JoinTable JT
        on ST.SourceTableID = JT.SourceTableID

    -- <Comment why this join is made, and why it's an left join>
    left join SecondJoinTable SJT
        on  ST.SourceTableID = SJT.SourceTableID
        and JT.Column3 = SJT.Column4

where

    -- comment why this filter is applied
    ST.SourceTableID = X

    -- comment why this filter is applied
    and JT.ColumnName3 = (
            select 
                somecolumn
            from 
                sometable
        )
;

优点:
- 注释是使代码可读并检测错误的重要部分。
- 在连接中添加“on”筛选器可以避免从内部连接更改为左连接时出现错误。
- 将分号放在新行上可以轻松地添加/注释where子句。


4

我倾向于使用类似于您的布局,不过我会更进一步,例如:

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

也许一开始看起来有些过分,但在我看来,这种使用制表符的方式基于SQL的声明性质,可以提供最清晰、最系统化的布局。
最终,你可能会得到各种各样的答案。最终,这取决于个人或团队达成的偏好。

3
我更喜欢在句子开头放逗号,这样更容易注释掉该字段,例如:--,ST.ColumnName1。Where 子句中也是一样,在使用 and 和 or 时更加方便。 - JeffO
我不明白这个注释掉的东西。如果你注释掉第一个选择值会怎样? - Wouter
1
很可能你需要注释掉列表末尾的一些行,通常包括最后一个值。第一个值通常是一个基本值,你几乎肯定会需要它。当然,这只是一种启发式方法,但大多数情况下确实是这样的。当然,SQL 实现应该忽略尾随逗号,就像大多数好的语言一样;-) - Adam Ralph
通过将一个常量表达式,如(0=0)或(0=1)添加到你的WHERE子句或长度JOIN条件中,使得第一个真实的条件可以在其前面加上AND/OR,从而将该可评论性好处提升到下一级。例如:"WHERE (0=0)" [newline] "AND ST.SourceTableID = X",然后如有需要注释您的每一行WHERE语句。 - BillVo
@BillVo:我相信这不会影响查询速度,但在我看来,我们不应该为了注释掉代码而添加无用的内容。 - user1071847

2

这篇帖子中有很多好的观点。我一直试图说服人们使用的一个标准是在每个列之前将逗号放在同一行上。像这样:

Select column1
   ,column2
   ,column3
   ,column4
   ,Column5 ...ect

相反的:

Select column1,
   column2,
   column3, ect...

我喜欢这种做法的原因是,如果需要的话,你可以注释掉一行代码,由于相应的逗号也被注释掉了,所以在运行时不会出现逗号问题。我知道在帖子中还有另一个用户也这样做了,但并没有特别指出。这不是一个很重要的发现,只是我的个人看法。谢谢。

1
如果您在末尾加逗号,那么当注释列表中的最后一行时,您只会遇到逗号问题。将逗号放在开头,那么当注释列表中的第一行时,您只会遇到逗号问题。 - Arin Taylor
但是注释掉第一行会导致问题,因为有Select关键字。 - user482745
我更喜欢在开头加逗号——在选择新字段时,这样阅读起来更容易,我个人认为。我也认为我更喜欢将 column1 缩进到自己的一行中。https://hackernoon.com/winning-arguments-with-data-leading-with-commas-in-sql-672b3b81eac9 - SherlockSpreadsheets

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接