前言:在数据治理领域的文章大多都是从业务角度进行切入,本文从纯技术角度切入,介绍了数据血缘关系的图分析思路、领域建模层面构建中间层的思路。以及纯粹从词法分析和语法分析的统计学层面维度。
图视角
受益之前在招商银行的担保圈项目启发,将对于图的分析观点带入到数据治理当中来。我们在DW中获取到数据的血缘关系表schema如下所示(血缘数据见附件,此处仅选取几块关键指标):
src_guid | dst_guid | edge_type | node_ids | process_type | main_table_flag | clt_src_type | dt |
---|
我们适度从指标中选取图节点的属性和边属性,实验将src_guid和src_db_type作为节点属性,将process_type和main_table_flag作为边的属性,由于边属性的两个数值仅存在唯一枚举值,在边的属性上不好大做文章,本文仅从节点之间的关系出发,谈谈图算法与数据治理的全貌。
由于MaxCompute不具备开发资源。此处我们自力更用开源图数据库Neo4j对数据的血缘关系全貌来进行分析,在我们创建好节点后,导入数据节点和边的关系展示如下所示:
数据治理节点定义
我们可以看到图中的节点呈现出一定的有向图分布特性,部分稀疏部分密集。我们可以通过具体的量化指标来对于节点的特性进行针对性处理。
入度(indgree):即有向图中以该节点为终点边的数目。
出度(outdgree):即有向图中以该节点为起始点边的数目。
强联通分量:如果一些定点中任意两个顶点都能互相到达(间接或直接),那么这些节点就构成了一个强联通分量,如果一个顶点没有出度,即它不能到达任何顶点则该顶点自身是一个强联通分量。强联通分量SCC算法详情
我们将图的特性应用到数仓领域得出结论如下:
- 一个节点表的入度和处度之和越大,说明该节点越重要,适合在梳理数据分层时优先梳理该节点。
- 如果一个节点 入度=0 且 出度!=0,说明该节点为源节点,可添加规范如:节点名称是否以ods开头等。
- 如果一个节点 出度=0 且 入度!=0,说明该节点为最终节点,可添加规范如:节点名称是否以adm开头等。
- 如果一个节点的入度出度!=0 入度>>出度,说明该节点有可能作为中间表又作为最终的指标项表,在数据治理的过程中可以将其中的部分指标项提取出来单独成表。
- 如果一个节点的入度出度!=0 入度<出度,说明该节点为中间表,可添加中间表相应规范进行检查。
- 如果图中存在表之间的互引用的情况,即上述所说的强联通分量,则针对性的重点处理,梳理清楚表之间的指标关系。
数据治理现状
针对上述关于数据治理的诊断思路,我们来看看香港银行数据治理的现状,首先就强联通分量即表之间的互相引用所展开:
setCount | maxCount | minSetsize |
---|---|---|
712 | 3 | 1 |
可见,经由强联通分量算法展开的715张表中,有712个强联通分量,其中大于1的强联通分量有两个,最大联通分量数为3。说明数据之间的互相引用状况良好,我们暂且来编写CQL来看一下这两个大于1的强联通分量图, 分别是:
match(t:Table) where t.componentId=*** return t
同时,我们也可以对于其中入节点和出节点来看到有多少张源表和adm数据集市表,相关的CQL如下:
MATCH (n:Table) with n, size((n)-[:process]->()) as OutDgree, size((n)<-[:process]-()) as InDgree set n.OutDgree=OutDgree,n.InDgree=InDgree;
match (n:Table) where n.OutDgree=0 return n
match (n:Table) where n.InDgree=0 return n
综上以出度为0可知共有354张集市表,以入度为0可知有134张ods源表。入度和出度不为0的表一共227张。
其中入度和出度最大相差为8,相差为8的表共两张分别是:odaps.bank_china_libra_new_acc_report、odaps.common_algo_details_di。这其中,按照前面理论所说的 重要性评分=入度+出度 原则,对表的重要性也进行了排序,最重要的 odaps.evt_trd_detail ,其重要性得分为49分。
领域模型视角
由于数据治理的工作展开不可能由一个人完成,故人员的投入和分工存在是当前摆在*银行工作中的一大问题。分工的方法有许多,可以按照业务系统的分工划分、按照业务线的上下游划分、或是从纯粹的技术视角给出建议。小明的思路是:对于中间层的建设按照系统的域的划分。
其实飞马模型的应用是十分复杂的,但是建模的结果不是在于简化,而是在于其是否能很好地描述问题。下图是账户域的领域模型,对于这类E—R模型一个最佳的思路就是:从E(实体)入手,结合R(关系)中的统计字段以形成大宽表来构成中间层的表结构设计。
统计学视角
基本概念:
- 抽象语法树 (Abstract Syntax Tree,AST) 抽象语法树是源代码结构的一种抽象表示,它以树的形状表示语言的语法结构。抽象语法树一般可以用来进行代码语法的检查,代码风格的检查,代码的格式化,代码的高亮,代码的错误提示以及代码的自动补全等等。
- 语法解析器 (Parser) 语法解析器通常作为编译器或解释器出现。它的作用是进行语法检查,并构建由输入单词(Token)组成的数据结构(即抽象语法树)。语法解析器通常使用词法分析器(Lexer)从输入字符流中分离出一个个的单词(Token),并将单词(Token)流作为其输入。实际开发中,语法解析器可以手工编写,也可以使用工具自动生成。
- 词法分析器 (Lexer) 词法分析是指在计算机科学中,将字符序列转换为单词(Token)的过程。执行词法分析的程序便称为词法分析器。词法分析器(Lexer)一般是用来供语法解析器(Parser)调用的。
常见的语法分析器
Druid、antlr、sqlparser 由于hive语法树是由antlr生成hive1.0 SQL语法规则:基于antlr。小明在测试之后结果如下所示,下图是选取真实的报表sql,对其进行拆分后的结果,我们可以看到对于sql的查询和子查询所包含的字段均以列表的形式进行了返回,甚至包含了字段的归属。对于这类条件查询语句,我们可以进行简单的统计来分析指标的使用频数、血缘关系以及分类字段。
select
a.ip_role_id,
b.account_no,
coalesce(regexp_replace(b.cert_name,"\\,","\\1 "),b1.cust_name) as cert_name,
coalesce(b.chinese_name,b1.cust_chn_name) as chinese_name,
case when b.currency='344' then 'HKD' else b.currency end as currency,
b.vtrl_card_id,
case when a1.cert_status='2' then '认证中'
when a1.cert_status is null then '已销户' end as cert_status_nm ,
b1.user_bind_mbl,
case when b3.ip_role_id is not null then 'Y' else 'N' end libra_f,
b.pd_nm,
b.previous_balance*0.01 as previous_balance,
b.base_closing_balance*0.01 as base_closing_balance,
case when b.account_status='T' then '正常'
when b.account_status='B' then '冻结'
when b.account_status='C' then '销户' end as account_status_nm,
to_char(b.au_opn_dt, 'yyyyMMdd') as opn_dt,
to_char(dateadd(b.au_opn_dt,60,'dd'),'yyyyMMdd') as opn_60d,
case when b.account_status='C' then 'C' else cast(60-datediff(to_date('${dt}','yyyyMMdd'),b.au_opn_dt,'dd') as string) end as open_60d_f,
to_char(coalesce(b.au_cls_dt,b2.au_cls_dt), 'yyyyMMdd') cls_dt,
case when c.onr_acct_nbr is not null then 'Y' else 'N' end as income_f,
case when c.vb_svf_cnt > 0 then 'Y' else 'N' end as vb_svf_f,
c.td_amt_sum,
c.td_cnt,
c.td_fail_cnt,
c.inside_td_amt_sum,
c.inside_td_cnt,
c.inside_td_fail_cnt,
to_char(a.gmt_create, 'yyyyMMdd') as role_opn_dt,
to_char(dateadd(a.gmt_create,60,'dd'),'yyyyMMdd') as role_opn_60d,
case when b.account_status='C' then 'C' else cast(60-datediff(to_date('${dt}','yyyyMMdd'),a.gmt_create,'dd') as string) end as role_open_60d_f,
to_char(a2.gmt_his, 'yyyyMMdd') as role_cls_dt
from (
select distinct ip_role_id,gmt_create
from hkods.ods_cu_role_member
where dt<=${dt}
and cert_status ='2' --近7天曾是认证中的role
) a
left join (
select ip_role_id,cert_status
from hkods.ods_cu_role_member
where dt=${dt} --今日的状态
) a1
on a.ip_role_id=a1.ip_role_id
left join (
select
ip_id
,ip_role_id
,null as gmt_his
from hkods.ods_cu_involved_party_role --仍有效的role
where dt=${dt}
union all
select
ip_id
,ip_role_id
,gmt_his
from hkods.ods_cu_involved_party_role_his --已被注销的role
where dt=${dt}
) a2
on a.ip_role_id=a2.ip_role_id
left join hkcdm.dwd_bank_hk_agt_dep_acc_info_dd b --所有(活期、存款宝)账户信息(包括有效、已销户)
on a.ip_role_id=b.ip_role_id
and b.dt=${dt}
left join hkadm.adm_rgl_hkbk_supv_cust_base_info_dd b1 --所有用户的电话信息(已销户的无联系方式)
on a2.ip_id=b1.ip_id
and b1.dt=${dt}
left join hkods.ods_depc_dep_ar b2 --手动销户的账户实际销户时间。因bak表没有同步记录,会导致dwd_bank_hk_agt_dep_acc_info_dd里的账户销户时间为空( owner 御飞)
on b.account_no=b2.fnd_au_id
and b2.dt=${dt}
left join (
select distinct ip_role_id
from hkcdm.dwd_bank_hk_agt_dep_acc_info_dd --确认role关联的存款宝账户状态
where dt=${dt}
and account_status!='C' --未销户
and pd_code='01011000700000000002' --存款宝账户
) b3
on a.ip_role_id=b3.ip_role_id
left join (
select
onr_acct_nbr,
sum(case when opst_org_nm in ('MYBKC1HK',
'YINTC1HK',
'AUTOC1HK',
'EPAYC1HK',
'FUSBC1HK',
'AIRRC1HK',
'APFSC1HK',
'HKTPC1HK',
'LIVIC1HK',
'MOXBC1HK',
'TNGLC1HK',
'UNICC1HK',
'WECHC1HK',
'WEDIC1HK',
'OCTOC1HK',
'PONCC1HK',
'KRILC1HK',
'TTFSC1HK',
'AABLC1HK',
'PAYMC1HK')--payME
then 1 else 0 end) as vb_svf_cnt,
sum(case when td_in_st='success' and td_out_st='success' then td_amt else 0 end)*0.01 as td_amt_sum,
count(1) as td_cnt,
sum(case when td_in_st!='success' or td_out_st!='success' then 1 else 0 end) as td_fail_cnt,
sum(case when biz_tp in ('31','01','21') and td_tp != '3102' and td_in_st='success' and td_out_st='success' then td_amt else 0 end)*0.01 as inside_td_amt_sum,
sum(case when biz_tp in ('31','01','21') and td_tp != '3102' and td_in_st='success' and td_out_st='success' then 1 else 0 end) as inside_td_cnt,
sum(case when biz_tp in ('31','01','21') and td_tp != '3102' and td_in_st!='success' or td_out_st!='success' then 1 else 0 end) as inside_td_fail_cnt
from hkods.ods_dp_td_stmt
where dt=${dt}
and biz_tp in ('31','01','19','21')
and td_in_out_f='0'
group by onr_acct_nbr
) c
on b.account_no=c.onr_acct_nbr
where a1.cert_status ='2'
or a1.ip_role_id is null
;
优点:快速查找 统一查询的指标口径。
缺点:无法识别 不同的查询句法表达同一含义这类情况。
引入ElasticSearch辅助分析
对于拆解开来的指标进行模糊搜索和统计查询,常用的开源工具即为ElasticSearch和其配套的Kibana工具。如图,对上述数据结构生成CSV文件格式如下,将测试数据导入到ES中。我们可以看到该字段在指标集的引用达到了4次。
source_tabke | dist_table | select_column | groupby_column | orderby_column | full_sql |
---|
总结: 在数据治理领域的文章大多都是从业务角度进行切入,本文从纯技术角度切入,介绍了数据血缘关系的图分析思路、领域建模层面构建中间层的思路。以及纯粹从词法分析和语法分析的统计学层面维度。随着业务量的增大,个人对于业务领域知识的局限性导致很难从全局维度查看当前数据治理变得困难,往往只有一个感性的好用不好用的直观感受,无法量化。
从血缘关系的角度入手,可以看到当前表之间引用合理性,同时可以添加相应的命名检测规则。从在线系统领域建模的角度入手,可以迅速了解业务底层数据以及逻辑,给飞马模型的划分提供决策依据。最后,从词法和语法分析的角度,结合开源的模糊搜索和统计工具,可以查看SQL的复用程度,从而对整个链路常用的查询进行针对性优化和中间表提取,从而简化逻辑,方便后边同事接手。
(备注:文章中表名已做混淆处理)