浅墨散人 浅墨散人
  • 基础
  • 设计模式
  • JVM
  • Maven
  • SpringBoot
  • 基础
  • Flask
  • Diango
  • Pandas
  • SqlAlchemy
  • Sqoop
  • Flume
  • Flink
  • Hadoop
  • Hbase
  • Hive
  • Kafka
  • Kylin
  • Zookeeper
  • Tez
  • MySQL
  • Doris
  • Chrome
  • Eclipse
  • IDEA
  • iTerm2
  • Markdown
  • SublimeText
  • VirtualBox
  • WebStrom
  • Linux
  • Mac
  • Hexo
  • Git
  • Vue
  • VuePress
  • 区块链
  • 金融
数据仓库
数据治理
读书笔记
关于我
GitHub (opens new window)
  • 基础
  • 设计模式
  • JVM
  • Maven
  • SpringBoot
  • 基础
  • Flask
  • Diango
  • Pandas
  • SqlAlchemy
  • Sqoop
  • Flume
  • Flink
  • Hadoop
  • Hbase
  • Hive
  • Kafka
  • Kylin
  • Zookeeper
  • Tez
  • MySQL
  • Doris
  • Chrome
  • Eclipse
  • IDEA
  • iTerm2
  • Markdown
  • SublimeText
  • VirtualBox
  • WebStrom
  • Linux
  • Mac
  • Hexo
  • Git
  • Vue
  • VuePress
  • 区块链
  • 金融
数据仓库
数据治理
读书笔记
关于我
GitHub (opens new window)
  • 数据仓库

    • 数据仓库
    • 数据仓库整体架构图及介绍
    • DW数据仓库与ODS的区别
    • 数仓开发规范
      • 简介
      • 为什么写这篇文章?
      • 分层规范
      • 编码规范
        • IDE统一
        • 缩进
        • 大小写
      • 注释规范
        • Shell脚本头部注释
        • Python脚本头部注释
      • Shell脚本规范
        • 基本
        • 注释
        • 变量
        • 缩进
        • 函数
        • if
        • 循环
        • Here document
      • SQL语句规范
        • 关键词
        • 名称
        • 结构体
      • Python编码规范
      • 公共目录规范
      • 分享的理念
    • 如何保证数据质量问题?
    • 如何减少\导数\需求?
    • Canal+Kafka+Hbase+Hive集成
    • 数据质量管理
  • DatawareHouse
2018-09-27
目录

数仓开发规范

# 简介

本文记录整理了本人在工作中的一些心得体会及个人见解。欢迎留言交流...

# 为什么写这篇文章?

相信每一个做数仓、ETL等开发的同学都遇到过这样的问题:

  1. 脚本写的乱,千人千面,每个人写法都不一样,各有各的风格。比如SQL里的逗号在前还是在后?
  2. 脚本放的到处都是,例如:很多目录都有script,tools等这样的重复文件夹
  3. 脚本风格的问题:不缩进,不换行,没有统一大小写等
  4. 世纪难题之一:命名

等等

以至于你看别人的代码,别人看你的代码都非常难受。 本文就以个人的观点从以下几个方面来说下数仓开发的一些规范。会根据情况不定时更新补充。

# 分层规范

这个没什么好说的,涉及到数仓架构范畴,什么功能的脚本放到什么地方。。

# 编码规范

# IDE统一

这个问题好像不是强制约定,但是为了同事之间方便交流等各种因素,还是建议大家使用统一的编辑器。 例如

  1. Python编辑器:Pycharm (opens new window)
  2. Shell编辑器:VS Code (opens new window)
  3. SQL客户端(可支持常见各种数据库):DataGrip (opens new window)

# 缩进

所有代码的缩进均不能使用tab键,必须使用4个空格代替一个tab键,主要是因为各个系统中tab键显示的效果不一样,可能会串行等。

警告

所有代码必须保持缩进,以4个空格代替一次缩进tab

可以在IDE中装一些来显示tab键的插件

# 大小写

  1. 所有SQL语句中的关键字必须大写
  2. 所有常量必须大写:包括Shell中的全局变量、Python中的全局变量等

# 注释规范

所有脚本必须有注释:Shell、Python、Java等各种代码脚本必须有头注释

# Shell脚本头部注释

参考

#!/bin/bash

# ========================================================================
# created info  : zfang 2019-01-09
# contact email : zfang@hillinsight.com
# exec example  : bash dw_payment_map.sh [param1 param2]
# describe      : 为了迁移兼容,先从dw_payment中组装出来clinic_payment_book_all表结构,当做临时表,待数据稳定后可直接使用dw_payment,废弃本脚本!!
# change log    :
# ========================================================================
1
2
3
4
5
6
7
8
9

# Python脚本头部注释

参考

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
-------------------------------------------------
@version    : v1.0
@author     : fangzheng
@contact    : zfang@hillinsight.com
@software   : PyCharm
@filename   : monitor_db.py
@create time: 2018/7/31 下午5:06
@describe   : 监控数据库状态,连接异常发送邮件
-------------------------------------------------
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Shell脚本规范

提示

参考Shell 编码规范 (opens new window)

# 基本

  1. 文件名全部小写,单词间以下划线连接,不可用驼峰
  2. 定义在所有函数之外的变量为全局变量。必须大写,并用readonly修饰

# 注释

Shell脚本里也必须有注释

  1. 头部注释:参考上面Shell脚本注释
  2. 行注释:重要或复杂代码均必须写行级注释

# 变量

  1. 全局变量:必须大写,readonly
#!/bin/bash

# ========================================================================
# created info  : zfang 2019-01-09
# contact email : zfang@hillinsight.com
# exec example  : bash dw_payment_map.sh [param1 param2]
# describe      : 为了迁移兼容,先从dw_payment中组装出来clinic_payment_book_all表结构,当做临时表,待数据稳定后可直接使用dw_payment,废弃本脚本!!
# change log    :
# ========================================================================

# 全局变量:必须大写
FILENAME=""

1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 局部变量:方法内的变量 必须加local关键字


 


function create_table
{
    local create_sql=""
}
1
2
3
4
  1. 变量引用: 所有的变量引用必须使用双大括号的方式${FILENAME}

# 缩进

Shell脚本里的缩进必须使用空格键,不可使用tab键。可在vs Code中装一些插件来处理

# 函数

  1. 函数的大括号需要换行
function create_table
{
    local create_sql=""
}
1
2
3
4
  1. 每个Shell脚本使用统一的模板(usage、main、耗时统计等)。特殊处理情况除外

参考脚本:


#!/bin/bash

# ========================================================================
# created info  : zfang 2019-02-19
# contact email : zfang@hillinsight.com
# describe      : 报表:中间表
# exec example  : bash middle_med_record.sh [param1,param2]
# change log    :
# ========================================================================

# 全局变量必须大写
DT="1970-01-01"
EXE_HIVE="hive"
DB="pet_medical"
TABLE="middle_med_record"
TABLE_NAME="${DB}.${TABLE}"

YESTERDAY=$(date -d "-1 day" +"%Y-%m-%d")

function usage
{
    echo "Usage : bash $0 [yyyy-mm-dd]"
}


function check_param
{
    # check your input param
    if [ $# -eq 1 ]; then
        DT=${1}
    else
        echo "======>The parameter is empty, using the default parameter!"
        DT=$(date -d "-1 day" +"%Y-%m-%d")
    fi
    echo "======>etl_date is ${DT}"
}


function create_table
{
    local create_sql=""
    $EXE_HIVE -e "${create_sql}"
    return $?
}

function load_data
{
    local load_data_sql=""

    $EXE_HIVE -e "${load_data_sql}"
    return $?
}

function main
{
    local start=$(date +%s -d "0 day ago")
    check_param "$@"
    create_table && load_data
    local run_stat=$?
    local end=$(date +%s -d "0 day ago")

    echo "执行完成,状态:${run_stat}, 耗时:$(expr $end - $start ) 秒"

    return ${run_stat}
}

main "$@"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# if

将 then 放在和 if 同一行。


if condition 1; then
    ...
elif condition 2; then
    ...
else
    ...
fi
1
2
3
4
5
6
7
8

# 循环

将 do 放在和 for 和 while 同一行。

for x in "foo" "bar" "quz"; do
    ...
done

while true; do
    ...
done
1
2
3
4
5
6
7

# Here document

需要写大段文本或模板时采用 here document。无需变量展开时结束符使用双引号。

function usage
{
    cat << EOT

Usage: $0 [options]

--help          Display this message and exit
--file FILE     Specify target filename, default is /etc/myapp.conf

EOT
}
1
2
3
4
5
6
7
8
9
10
11

# SQL语句规范

提示

参考

  1. www.sqlstyle.guide简体中文 (opens new window)
  2. sql-style-guide (opens new window)

# 关键词

关键字应该是大写。

/* Good */
SELECT COUNT(1) FROM tablename WHERE 1;

/* Bad */
select count(1) from tablename where 1;
1
2
3
4
5

# 名称

每个子句应该开始一个新的行。SELECT,JOIN,LEFT JOIN,OUTER JOIN,WHERE,UNION等是开始新子句的关键字。

/* Good */
SELECT COUNT(1)
  FROM tablename
 WHERE really_loooong_column = CONCAT(other_column, ' street');

/* Bad */
SELECT COUNT(1) FROM tablename WHERE really_loooong_column = CONCAT(other_column, ' street');
1
2
3
4
5
6
7

开始子句的关键字应该是右对齐的。我们的想法是在关键字和对象之间创建一个单一的字符列。

/* Good */
SELECT COUNT(1)
  FROM tablename
 WHERE 1;

  SELECT key_column, COUNT(1)
    FROM tablename
GROUP BY key_column;

/* Bad */
SELECT COUNT(1)
FROM tablename
WHERE 1;
1
2
3
4
5
6
7
8
9
10
11
12
13

子查询应该对齐,就像开括号是0列那样,它们应该作为一个单元缩进,以将它们标识为子查询。他们应该继续将开放关键字右对齐。

/* Good */
SELECT *
  FROM (  SELECT candidates.name, count(1)
            FROM candidates
            JOIN votes ON candidates.id = votes.candidate_id
        GROUP BY candidates.name) name_count
  JOIN city c ON name_count.name = c.mayor;

/* Bad */
SELECT *
  FROM (SELECT candidates.name, count(1)
    FROM candidates
    JOIN votes ON candidates.id = votes.candidate_id
    GROUP BY candidates.name) name_count
  JOIN city ON name_count.name = city.mayor;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 结构体

列别名应始终使用关键字AS当查询具有多个列别名的多个列时,这变得很重要。如果没有AS关键字,删除的逗号会使两列成为单个别名列。

/* Good */
SELECT ebe_ebs_sox_flag_set_for_all_crs AS sox_ok
  FROM tablename;

/* Bad */
SELECT ebe_ebs_sox_flag_set_for_all_crs sox_ok
  FROM tablename;
1
2
3
4
5
6
7

表别名和列别名应该是描述性的。与变量名称非常相似,“a”,“b”,“x”等在短示例之外通常不是有用的。

表别名的微小名称有时可以用作缩写。例如,如果频繁引用“版本”,则将其缩写为“r”可能是有意义的。但是,“rel”几乎一样短,而且更具描述性。有一个很好的理由“r”而不是“rel”。

子查询别名应该更具描述性。子查询有效地在内存中创建临时表。因此,如果你将它命名为“x”,那么绝对没有任何东西可以向后来的维护者提出表格背后的意图。

/* Good */
SELECT *
  FROM (SELECT table1.id AS child, 
               table2.id AS parent
          FROM table1
          JOIN table2 ON (table2.parent_id = table1.id) ) parentage;

/* Bad */
SELECT *
  FROM (SELECT table1.id AS child, 
               table2.id AS parent
          FROM table1
          JOIN table2 ON (table2.parent_id = table1.id) ) x;

/* Bad */
SELECT *
  FROM (SELECT table1.id AS child, 
               table2.id AS parent
          FROM table1
          JOIN table2 ON (table2.parent_id = table1.id) ) link;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

缩写对子查询别名没有帮助

# Python编码规范

提示

参考

  1. elements-of-python-style (opens new window)
  2. Python风格规范— Google 开源项目风格指南 (opens new window)
  3. 个人总结——全面的『Python编码规范』 (opens new window)

# 公共目录规范

在数仓的开发中,经常遇到这种情况,因为是集群,所以可能会有个公共用户(例如:hadoop)之类的来存放所有的脚本 但是这个用户是数据组的同学共享的,都需要使用到,在跑脚本时就会产生各种临时文件。所以可以按相应的规范来约定一些临时文件的存储路径

  1. ${脚本根目录}/tmp:临时目录(文件以.tmp结尾)

    存放临时生成的文件,意味着可以删除

  2. ${脚本根目录}/log:日志文件(文件以.log结尾)

存放所有脚本的一些输出日志,以模块分文件夹 例如:

  • ${脚本根目录}/log/customer/
  • ${脚本根目录}/log/app/

# 分享的理念

这里要说的是导数的部分,数据组做的很频繁的工作也有导数的工作。那么一个导数需求A分配给一个数据同学小马后,小马导完数,假如过段时间又需要导数,而小马在忙其他很重要的事,或者请假了,为了避免导数延时,其实应该把每个人导数的代码也分享出来。

规范:

  1. ${数仓脚本根目录}/导数记录/yyyyMMdd/姓名_需求名称/姓名_需求名称.sql
  2. ${数仓脚本根目录}/导数记录/yyyyMMdd/姓名_需求名称/姓名_需求名称.shell

例如:导出orgid=1234的2019年3月的所有消费客户数

${数仓脚本根目录}/导数记录/yyyyMMdd/小马_orgid=1234机构2019年3月所有消费客户数/1234机构2019年3月所有消费客户数.sql

这样的话,可以有以下好处:

  1. 代码复用:下次有类似的需求就可以直接参考该目录SQL,而不用再让别的同学做一遍这个工作
  2. 代码审核:导数的SQL可以上传到Git,这样大家(项目经理)也可以一起检查。
  3. 历史追溯:防止需求方后续再次问你一些本需求的问题(对数), 说导的数和需求不符合之类的
#DataWareHouse
最后更新时间: 2023/11/23 14:03:35
DW数据仓库与ODS的区别
如何保证数据质量问题?

← DW数据仓库与ODS的区别 如何保证数据质量问题?→

最近更新
01
分区分桶
08-21
02
数据模型(重要)
08-21
03
安装和编译
08-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式