使SQL更易于阅读的几个小技巧


 



无论是数仓开发还是数据分析,写一手好的SQL是一项基本的技能。毋庸置疑,编写性能较好的SQL是非常重要的,但是,SQL的可读性同样是不容小觑的。一个有着混乱格式的SQL脚本,往往需要花费较长的时间去弄清楚脚本的具体逻辑。如果你曾经被祖传的毫无章法的SQL脚本狂虐过,你一定心有感触。本文将分享几个SQL格式的规范,当然仁者见仁智者见智,其实没有严格的标准,如果有,那就是保证易于阅读和易于维护。


大小写保持一致


可以对SQL关键字使用不同的大小写,但是要保持一致。看看这个:


SELECT customer_city,count(*) from dim_customer WHERE customerProvince = '上海' Group by customer_city

上面的SQL语句是不是很让人抓狂,大小写混用,看起来很不规范。总结起来,要注意下面几点:



  • SQL的关键字可以大写,也可以小写,但是不要大小写混用。上面的SQL查询既有完全大写,也有首字母大写,更有小写。看似是不拘小节,但是万万使不得。
  • 由于大小写是混合的,因此很难区分小写的关键字实际上是关键字还是列。此外,阅读也很烦人。
  • 字段命名要保持一致的风格,上面的SQL与中customer_city是小写加下划线,而customerProvince字段是驼峰命名法,这种不一致性显然是不可取的。

进行一些规范之后后,查询应如下所示:


SELECT customer_city,
   count(*)

FROM dim_customer
WHERE customer_province = '上海'
GROUP BY customer_city


使用缩进


再来看看下面的一条查询语句:


SELECT dp.region_name,count(*) FROM user_behavior_log ubl JOIN dim_province dp ON ubl.province = dp.province_name WHERE ubl.province = '上海市' GROUP BY dp.region_name

将上面的SQL语句格式化下面的形式:


SELECT dp.region_name, count(*)
FROM user_behavior_log ubl
JOIN dim_province dp ON ubl.province = dp.province_name
WHERE ubl.province = '上海市'
GROUP BY dp.region_name

上面的格式化形式似乎清晰了很多,但是如果语句中包含了子查询、多个JOIN以及窗口函数时,同样会显得对阅读不是很友好。


再换一种格式化方式,如下:


SELECT
dp.region_name, 
count(*)

FROM user_behavior_log ubl

JOIN dim_province dp ON ubl.province = dp.province_name

WHERE ubl.province = '上海市'
GROUP BY

dp.region_name

-- 或者下面的形式
SELECT

dp.region_name 
,count(*)

FROM user_behavior_log ubl

JOIN dim_province dp ON ubl.province = dp.province_name

WHERE ubl.province = '上海市'
GROUP BY

dp.region_name</code></pre>

尖叫提示:对于第二种形式,在SELECT字段中,从第二个字段开始,每个字段前面添加一个逗号,而不是每个字段后面使用逗号结尾。这种方式可以很方便地识别FROM前面是否存在逗号,从而造成语法错误。当然,这个只是个人习惯问题,并不是硬性的规定。


另外上面的SQL语句使用了4个字符缩进,当然也可以选择2个字符缩进,这个也是个人习惯问题。


在group by 和order by之后使用字段的排列序号


同样,这种书写风格也是个人的一种偏好,并不是一条硬性规定。应该有很多的初学者对此种写法并不是很清楚。


看下面的这条SQL:


SELECT
dp.region_name, 
dp.province_name,
count(*)

FROM user_behavior_log ubl

JOIN dim_province dp ON ubl.province = dp.province_name

GROUP BY

dp.region_name,
dp.province_name

ORDER BY

count(*) desc -- Hive不支持</code></pre>

可以写成下面的形式:


-- 注意:MySQL、Impala支持这种写法,Hive不支持

SELECT

dp.region_name, 
dp.province_name,
count(*)

FROM user_behavior_log ubl

JOIN dim_province dp ON ubl.province = dp.province_name

GROUP BY 1,2
ORDER BY 3


这样写有如下的好处:



可以节省行:通过许多字段进行分组不仅会在SELECT子句中添加更多行,还会在GROUP BY和ORDER BY子句中添加更多行,甚至可能使查询中的行数增加一倍。
可维护性:如果想改变分组字段,只需在SELECT子句中进行操作,在GROUP BY语句中不需要修改。
方便:只需要GROUP BY 1,2,3,…,n,其中n为分组列的字段序号。

使用Common Table表达式(with语句)


该方式称之为Common Table Expressions(CTE),用来简化复杂查询。它们可以定义为临时视图,因为它们仅在整个查询执行期间存在。


看一个简单的例子:


-- 注意Hive、Impala支持这种语法,低版本的MySQL不支持(高版本支持)
WITH employee_by_title_count AS (
SELECT
    t.name as job_title
    , COUNT(e.id) as amount_of_employees
FROM employees e
    JOIN job_titles t on e.job_title_id = t.id
GROUP BY 1

),
salaries_by_title AS (

 SELECT
     name as job_title
     , salary
 FROM job_titles

)
SELECT *
FROM employee_by_title_count e

JOIN salaries_by_title s ON s.job_title = e.job_title</code></pre>

上面的语句中,最终的查询使用employee_by_title和salaries_by_title的两个结果集进行JOIN产生最终结果。这比在SELECT子句中或直接在FROM子句中进行子查询更具可读性和可维护性。


使用具有描述性的别名


这一点非常重要,如果查询的列字段很多,肯能会存在一些id,count(*)等,很难辨识代表什么含义,所以需要为每个查询列加上可读的、易于理解的别名,能够让其他人一眼就能看出代表什么含义,这样可以增加脚本的可维护性。


总结


文中提到的一些规范有些是必须要遵守的,有些是个人的编码习惯,无论你是开发人员、数据分析师、数仓开发,遵循一些规范可以避免不必要的麻烦。值得注意的是,关于SQL的格式,没有一个标准的约定,需要与团队的其他成员达成共识,一起按照相同的约定进行开发,从而可以大大提高脚本的可读性和可维护性。





本文作者:西贝木土



上一篇:PostgreSQL参数学习:wal_keep_segments


下一篇:SpringBoot2 整合FreeMarker模板,完成页面静态化处理