数据仓库之血缘关系

时间:2024-11-13 12:22:23

血缘关系是什么

一提到“血缘关系”这个词,我第一个想到是族谱的样子。下图就是一个简单的族谱。
来源于网络
血缘关系概念:/zhangleisanshi/p/

一个宗族有了族谱,那么就比较容易找到自己的祖先,了解自己祖先的丰功伟业,沿着祖先的足迹再起航。

对数仓了解的人应该知道,数据仓库是从业务系统(例如,用友软件、融通、红草)、爬虫等等数据源哪里获得的数据,在设计业务系统的时候,通常会遵循三范式的原则,三范式原则将数据内聚到一个个的表里面,而到了数仓里面,我们做了一个反向的操作,我们是把高内聚的数据耦合到一个或者几个表里,这就出现了星型数仓。

我们如果想做数仓的话,我们需要先梳理清楚计算各个指标、维度的时候利用到那些业务的表,也就是 ods 仓和 dw、dm 层的字段关系映射表,这个映射表其实就构成了表与表之间的血缘关系。

血缘关系的应用

有了血缘关系,我们就可以获得下面几点好处。

  • 根据表与表之间的依赖关系,在上游数据跑批出现错误的时候,可以自动跑下游的数据。
  • 通过字段级别的血缘关系,了解字段的计算公式,降低对数仓的学习成本。
  • 自动生成数据地图、数据字典
  • 代码规范的检查

另外,从开发效率的角度,总结如下:

团队的人数多起来以后,每个人都有自己书写 sql 或者其他程序的习惯、文件名称的命名习惯、项目目录的习惯、数据分层习惯。这会导致以下问题:

  1. 表和表之间的依赖关系只能找开发的模型货报表的开发工程师来看源代码
  2. 字段的计算逻辑也只能看源码
  3. 代码规范不统一,增加了代码的可阅读性。
  4. 手工查看字段计算口径、表之间的依赖关系,这种方式占用了开发工程师的时间,降低了
    开发效率。

做血缘关系的思路

熟悉编译原理的同学都知道,我们可以开发编译器来读代码,然后翻译成机器可以执行文件。基于这种思路,我们也可以写个编译器,让他来读 query 语句。然后自己找到表和表之间的依赖关系。举个例子:

insert into DB.targetTable
select columns ...
  from DB.sourceTable1
 inner join DB.sourceTable2
where ...

上面是一个普通 insert 语句,我们在做报表开发的时候经常使用这种语法,我相信一定可以吃使用程序自动的的判断出 targetTable 是由 sourceTable1 和 sourceTable2 生成的。再进一步我们可以分析出 targetTable 中的字段是从 sourceTable1 和 sourceTable2 中的那个字段来的,又经过了那些计算过程。

说完为什么做血缘关系以及解决问题的思路,我们再进一步说一下具体的的解决问题的方案。

具体的方案

首先要解决的问题是 sql 脚本管理的问题,我们需要将所有人的代码放到一个地方统一的管理,这样我们才能方便得获所有人的脚本,进而分析出血缘关系。这就用到鼎鼎大名的 git,如果没有数据安全方面的考虑,我们可以将 sql 脚本放到 github 上,但是一般不会这样的。我们可以自己搭建一个 gitLab ,自己管理代码。

其次是代码规范的问题。例如,我们使用 shell 来书写调度逻辑的,那么我最好给出一个模板。这样就可以做到代码规范了。下面就是一个例子:

#!/bin/bash 

currDir=$(cd `dirname $0` ; pwd)

# initial parameter

[hive | presto-client] $currDir/

# catch some exception and handle it

如果大家按照上面的格式写,我们可以很容易判断出 query 的运行平台。举一个反例子。

A 同学写的 shell 脚本如下:

#!/bin/bash 

currDir=$(cd `dirname $0` ; pwd)

# initial parameter

hive $currDir/

# catch some exception and handle it

B 同学写的 query 如下:

#!/bin/bash 

currDir=$(cd `dirname $0` ; pwd)

# initial parameter
sqlFileName=""
hive $currDir/sqlFileName

# catch some exception and handle it

如果像这样的话,会给我们解析 query 的运行平台带来麻烦。我们先要找到 .sql结尾的文件,然后使用文件名字在.sh的文件中查找,如果我们一行一行的读取.sh的文件,那就不太好找到调度 query 的 shell 文件了。

我上面提到的只是一个例子,其实想这样的规范还有很多,一个优秀的团队应该有比较严谨的代码规范。

上面我们提到使用 sql 的解析器来分析出表和表之间的依赖关系。其实,像 hive、presto 已经有了这样的解析器了。hive、presto这样的计算平台都是把 sql 解析当成单独的模块开发。在他们的 maven 项目中,也是单独的一个模块,我们只需要简单的引入 sql 解析器 maven
依赖就可以了。使不出爽歪歪(但是提供便利的同时,也给我们设置了知识的壁垒,例如,我们对编译原理的东西就一无所知了,所以还学习 anltr 这个框架,毕竟hive presto 都用了)

如果我们将表抽象成图里面的一个点,表和表之间的依赖关系就图中的线。

血缘关系的“点”需要有字段:

  • 执行平台:hive、clickhouse、presto
  • 脚本所在的位置:/data/sql_code/dm/dm_xxx.sh
  • 源数据表:source_table1、source_table2
  • 表名称

血缘关系中“线”的字段:

  • sourceNode:源数据表或者字段的名字
  • targetNode: 目标表或者字段的名字

下面设计一下表结构:

create database blood DEFAULT CHARACTER SET utf8;
use blood ;
CREATE TABLE `blood_node` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) DEFAULT NULL COMMENT '父id',
  `execute_platform` varchar(50) DEFAULT NULL COMMENT '脚本执行平台',
  `script_path` varchar(200) DEFAULT NULL COMMENT '脚本所在的路径,格式为:服务器IP:port:path',
  `source_table` varchar(2000) DEFAULT NULL COMMENT '源数据表',
  `table_name` varchar(255) DEFAULT NULL COMMENT '表名称',
  `updated_at` timestamp  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  COMMENT '数据更新时间',
  PRIMARY KEY (`id`),
  KEY `table_name_index` (`table_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
ALTER table blood.blood_node ADD INDEX table_name_index(table_name);

查询血缘关系的 query 语句:


    select parent.table_name
          ,parent.execute_platform 
          ,parent.script_path      
          ,child.table_name
          ,child.execute_platform 
          ,child.script_path             
      from blood.blood_node as parent
inner join blood.blood_node as child
        on parent.id = child.pid
     where parent.table_name = 'table_name'