一:数据仓库(概述)
所有的表设计都要参照业务总线矩阵
三: 维度建模理论之事实表
事实表通常比较“细长”,即列较少,但行较多,且行的增速快。
事实表作为数据仓库维度建模的核心,紧紧围绕着业务过程来设计。其包含与该业务过程有关的维度引用(维度表外键)以及该业务过程的度量(通常是可累加的数字类型字段)。
事实表主要包含维度外键和度量
事务事实表,周期快照事实表,累计快照事实表
选择业务过程→声明粒度→确认维度→确认事实(指的是每个业务过程的度量值(通常是可累加的数字类型的值,例如:次数、个数、件数、金额等)
事务事实表用来记录各业务过程,它保存的是各业务过程的原子操作事件,即最细粒度的操作事件。粒度是指事实表中一行数据所表达的业务细节程度。
典型的粒度声明如下:订单事实表中一行数据表示的是一个订单中的一个商品项。
周期快照事实表以具有规律性的、可预见的时间间隔来记录事实
定期同步一份全量数据到数据仓库,构建周期型快照事实表,就能轻松应对此类统计需求,而无需再对事务型事实表中大量的历史记录进行聚合了。
1>确认粒度 (可由采样周期和维度描述 维度由统计指标决定)
2>确认事实 (根据统计指标决定 比如商品库存)
事实类型:可加事实,半可加事实和不可加事实
累积型快照事实表
累积型快照事实表主要用于分析业务过程(里程碑)之间的时间间隔等需求。例如前文提到的用户下单到支付的平均时间间隔
选择一个业务流程中需要关联分析的多个关键业务过程,多个业务过程对应一张累积型快照事实表
四:维度建模理论之维度表
事实表紧紧围绕业务过程进行设计,而维度表则围绕业务过程所处的环境进行设计。维度表主要包含一个主键和各种维度字段,维度字段称为维度属性。
4.1:维度表设计步骤
1> 确定维度(可能存在多个事实表与同一个维度都相关的情况,这种情况需保证维度的唯一性,即只创建一张维度表,如果某些维度表的维度属性很少,则可不创建该维度表,而把该表的维度属性直接增加到与之相关的事实表中,这个操作称为维度退化)
2>确定主维表和相关维表
3>确定维度属性(确定维度属性即确定维度表字段,维度属性主要来自于业务系统中与该维度对应的主维表和相关维表)
① 尽可能生成丰富的维度属性 (维度属性的丰富程度直接影响到数据模型能够支持的指标的丰富程度)。 ② 尽量不使用编码,而使用明确的文字说明,一般可以编码和文字共存。③尽量沉淀出通用的维度属性(例如需要通过多个字段拼接得到。为避免后续每次使用时的重复处理,可将这些维度属性沉淀到维度表中)
规范化与反规范化:
规范化是指使用一系列范式设计数据库的过程,其目的是减少数据冗余,增强数据的一致性。通常情况下,规范化之后,一张表的字段会拆分到多张表。
反规范化是指将多张表的数据冗余到一张表,其目的是减少join操作,提高查询性能。
在设计维度表时,如果对其进行规范化,得到的维度模型称为雪花模型,如果对其进行反规范化,得到的模型称为星型模型。
采用雪花模型,用户在统计分析的过程中需要大量的关联操作,使用复杂度高,同时查询性能很差,而采用星型模型,则方便、易用且性能好。所以出于易用性和性能的考虑,维度表一般是不规范化的。
维度变化:
维度属性通常不是静态的,而是会随时间变化的,数据仓库的一个重要特点就是反映历史的变化,所以如何保存维度的历史状态是维度设计的重要工作之一。保存维度数据的历史状态,通常有以下两种做法,分别是全量快照表和拉链表。
全量快照表(离线数据仓库的计算周期通常为每天一次,所以可以每天保存一份全量的维度数据)和拉链表(更加高效的保存维度信息的历史状态)的作用是保存维度数据的历史状态
多值维度:(一条记录在某个维度表中有多条记录与之对应)
(例如,下单事实表中的一条记录为一个订单,一个订单可能包含多个商品,所会商品维度表中就可能有多条数据与之对应。)
第一种:降低事实表的粒度,例如将订单事实表的粒度由一个订单降低为一个订单中的一个商品项。第二种:在事实表中采用多字段保存多个维度值,每个字段保存一个维度id。这种方案只适用于多值维度个数固定的情况。
维度表多值属性:
(例如商品维度的平台属性和销售属性,每个商品均有多个属性值,例如商品维度的平台属性(平台提供的商品属性)和销售属性(店家为每件商品提供的属性),每个商品均有多个属性值)
针对这种情况,通常有可以采用以下两种方案:
①:将多值属性放到一个字段 ②: 将多值属性放到多个字段(这种方案只适用于多值属性个数固定的情况)
五:数据仓库设计
优秀可靠的数仓体系,需要良好的数据分层结构。合理的分层,能够使数据体系更加清晰,使复杂问题得以简化。
5.1:数据仓库构建流程
(一行代表一个业务过程 一列代表一个维度)
5.2.1 数据调研(业务调研和需求分析)
业务分析要注意,什么样的业务过程,处理的什么业务,业务的流程是怎样的,以及业务产生了哪些影响(注意业务 变化和影响)
需求分析(加购操作应该从binlog日志中找)
需要明确需求所需的业务过程及维度,例如该需求所需的业务过程就是买家下单,所需的维度有日期,省份,商品品类。
做完业务分析和需求分析之后,要保证每个需求都能找到与之对应的业务过程及维度。
总结:
5.3: 明确数据域(数据仓库模型 横向分层 纵向分数据域)
根据业务过程进行划分,需要注意的是一个业务过程只能属于一个数据域。
5.3.1:构建业务总线矩阵
业务总线矩阵中包含维度模型所需的所有事实(业务过程)以及维度,以及各业务过程与各维度的关系。矩阵的行是一个个业务过程,矩阵的列是一个个的维度,行列的交点表示业务过程与维度的关系。
一个业务过程对应维度模型中一张事务型事实表,一个维度则对应维度模型中的一张维度表。所以构建业务总线矩阵的过程就是设计维度模型的过程。但是需要注意的是,总线矩阵中通常只包含事务型事实表,另外两种类型的事实表需单独设计。
5.4:明确统计指标
1>原子指标 (基于某一业务过程的度量值 原子指标包含三要素,分别是业务过程、度量值和聚合逻辑。例如订单总额就是一个典型的原子指标)
2>派生指标
3>衍生指标
派生指标:
绝大多数的统计需求,都可以使用原子指标、派生指标以及衍生指标这套标准去定义。同时能够发现这些统计需求都直接的或间接的对应一个或者是多个派生指标。
汇总模型设计:
事实表存储在DWD层,维度表存储在DIM层。
汇总表与派生指标的对应关系是,一张汇总表通常包含业务过程相同、统计周期相同、统计粒度相同的多个派生指标。
六:数据仓库环境准备
启动NameNode进程的用户是谁,谁就是HDFS的超级用户
七:数仓开发之ODS层
ODS层存储的是原始数据,不会改变数据的结构
zcat专门用来查看gzip压缩的文件
日志文件分为三种:日志文件,业务数据(全量表(全量表的数据是由DataX传过来的)和增量表)
ODS层要保存全部历史数据,故其压缩格式应选择压缩比较高的,此处选择gzip。
7.1:用户行为日志表(设计思路)
1:每一行是一个json字符串
hive中有一个函数getJsonobject()专门处理json()(但是此方法存储json字符串,查询起来比较臃肿不建议)
hive中有json表可以将json映射为表中一个字段
Hive中的json表:
show create table gamll.student 查看创建表的语句
7.1.2:日志表设计
日志数据结构的复杂在于有嵌套,解析比较麻烦。
字段设置个数以一级字段为主
Hive中复杂的数据类型array,struct(每个字段可以设置自己的类型),map(键的类型相同,值的类型相同)。
7.1.3:日志表设计
日志表的设计要全面,不仅有页面结构的日志,还有启动结构的日志。
如果字段不一致,查询出来是null;
Hive中的表应该从三个维度分析:行,列和分区
数仓中的所有表都是外部表(只删除元数据)
7.1.4:数据装载
要考虑,每一天数据的流向(从哪里来和数据去向),当前业务,应该是,从HDFS
某一天的目录下的文件到,对应日期分区表中。
因为日志数据是增量数据每天都会执行,因此可使用脚本进行同步数据。
对于load data 是移动数据,因此不能对一份文件夹中的数据移动两次,因为第一次移动走之后,第二次不能再次移动。
八:ODS层业务表—全量表
hive中默认的空值的存储格式为\N,该业务表空值处理 Null Defined AS ‘’ 将空值改为空字符串(因为将来这个表中的数据是由DataX从Mysql传到HDFS上的,Mysql中有空值null,正常情况下需要转化成\N,在分析数据的时候才能被hive识别,但是DataX直接将MySQL的空值null存成了''空字符串,而且没有提供更改的途径,因此只能在hive端进行修改)
九:ODS层业务表—增量表
其它三种数据类型是inset ,update,delete对于binlog日志进行的数据处理。
old字段使用map key 和value都使用string,因为old字段更改的字段不一样,不容易确定。
十:维度模型设计思路
维度表的设计步骤:
1>确定维度 (有哪几张维度表,维度退化)
2>确定主维表和相关维表 (主维表和相关维表均指业务系统中与某维度相关的表)
3>确定维度属性
十一:商品维度表数据装载思路分析
计算任务幂等性,不管计算多少次,每一次的计算结果都是一样的
第9章 数仓开发之DWD层(DWD层存放的是事实表)
1> ① 业务过程 ② 粒度 ③ 维度 ④ 事实(度量值)
2> 列式存储 计算查询效率
3> 命名规则:dwd_数据域_表名_单分区增量全量标识(inc/full)
事务型事实表采用增量分区inc 周期快照事实表全量分区full
使用哪一种join关联方式:①两个表之间的包含关系 ②我们需要哪些数据
每一个需求的流程:①创建表 ②分析表中字段的来源 ③从需要的n张维度表中选择子查询的字段 ④确定连接的方式 ⑤结合需要创建的表 从子查询中找到对应的字段 ⑥ 判断是否需要对字段进行修改
数据装载:先明确数据的流向
案例:加购事务事实表
分析流程 ① 分析创建表语句
②:分析数据流向(数据来自哪里 以及会造成的影响)
加购的数据类型是insert
加购事务事实表思路分析
业务总线矩阵:
date_id只有年月日(维度外键) create_time 年月日时分秒(具体的加购时间)
因为这是加购操作,因此要结合cart-info表推断这三个字段的来源
可以根据source_type来源,分析比如促销活动的成效,智能推荐算法的效果
source用户下单的来源,可以理解为加购的环境,即 用户什么时间通过什么渠道下单了哪件商品,属于维度,因此此处属于维度退化。
维度的设计原则,尽量不使用编码,要用文字和编码结合。
source_id 意思是具体的哪个活动或者哪个推荐算法等等
需求调研:调研每个业务过程会对哪些数据产生影响(才能知道数据的来源),以及产生什么样的影响。
cart-info这张表即做了全量(full服务于周期快照表)又做了增量(需要保存每个明细操做,记录到bin-log才能记录每个细节的数据操作,服务于事务事实表,)
增量表首日都是做全量
增量表是根据maxwell的bootstrap功能做的,bootstrap功能是基于查询(从mysql中一条一条查出来然后写到分区里边)实现的。
因此通过第一天的数据,是无法拿到具体的加购操作的
一行数据代表,谁在什么时候把哪件商品加到了购物车
create-time(当我们第一次往购物车加入某件商品的时候的时间)和operate-time(当我们把同样的商品再次加入到购物车中,造成的影响是,这个商品的数量会加一)因此create-time并不能真正的代表一次加购操作,但是因为是第一天的数据,我们无法拿到,只能妥协一下,把create-time作为一次加购操作的时间。
事务事实表的处理思路是一样的,第一天需要动态分区将数据写入到不同的分区中,从第二天开始,每一天的数据写入到当天的分区中。
做每日同步的时候,一定要明确,加购的操作对哪些数据造成了 什么样的影响。
加购操作有两种情况,第一次添加一件新的商品会对create-time产生影响, 如果添加第二件同样的商品会对operate-time造成影响。
每日增量表与首日增量表需要查询的字段应该是一样的。
9.1:DWD退单事务事实表
全量同步基于查询
退单表与订单表关联的原因
9.2:周期快照表购物车库存top10
开窗函数,如果只有over(partition by)窗口大小从第一行到窗口的最后一行,如果为over(parttition by order by )从窗口的第一行到当前行。
启动日志,一行对应一个启动操作
hive on spark的第二个bug之list类型
9.3:流量域页面浏览事务事实表
一行代表一个页面浏览记录。
(表结构,数据流向)
(数据仓库上线之前,往往会存在一定的历史数据,业务数据往往只存在于业务数据库中,而通常的买点日志是没有历史数据的,因此我们要处理的表中也只包含一天的数据,因此可知首日和每日的数据装载思路大致是一样的)
流量域的数据都是与日志相关的,埋点的行为,一个一个的用户浏览记录。
一个完整的页面日志:
页面日志,以页面浏览为单位,即一个页面浏览记录,生成一条页面埋点日志。一条完整的页面日志包含,一个页面浏览记录,若干个用户在该页面所做的动作记录,若干个该页面的曝光记录,以及一个在该页面发生的报错记录。除上述行为信息,页面日志还包含了这些行为所处的各种环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等
{ "common": { "ar": "230000", "ba": "iPhone", "ch": "Appstore", "is_new": "1", "md": "iPhone 8", "mid": "YXfhjAYH6As2z9Iq", "os": "iOS 13.2.9", "uid": "485", "vc": "v2.1.134" }, "actions": [{ "action_id": "favor_add", "item": "3", "item_type": "sku_id", "ts": 1585744376605 } ], "displays": [{ "displayType": "query", "item": "3", "item_type": "sku_id", "order": 1, "pos_id": 2 }, { "displayType": "promotion", "item": "6", "item_type": "sku_id", "order": 2, "pos_id": 1 }, { "displayType": "promotion", "item": "9", "item_type": "sku_id", "order": 3, "pos_id": 3 }, { "displayType": "recommend", "item": "6", "item_type": "sku_id", "order": 4, "pos_id": 2 }, { "displayType": "query ", "item": "6", "item_type": "sku_id", "order": 5, "pos_id": 1 } ], "page": { "during_time": 7648, "item": "3", "item_type": "sku_id", "last_page_id": "login", "page_id": "good_detail", "sourceType": "promotion" }, "err": { "error_code": "1234", "msg": "***********" }, "ts": 1585744374423 }
启动日志:
启动日志以启动为单位,及一次启动行为,生成一条启动日志。一条完整的启动日志包括一个启动记录,一个本次启动时的报错记录,以及启动时所处的环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。
{ "common": { "ar": "370000", "ba": "Honor", "ch": "wandoujia", "is_new": "1", "md": "Honor 20s", "mid": "eQF5boERMJFOujcp", "os": "Android 11.0", "uid": "76", "vc": "v2.1.134" }, "start": { "entry": "icon", "loading_time": 18803, "open_ad_id": 7, "open_ad_ms": 3449, "open_ad_skip_ms": 1989 }, "err":{ "error_code": "1234", "msg": "***********" }, "ts": 1585744304000 }
https://issues.apache.org/jira/browse/HIVE-21778 bug官网解析
9.2:用户域用用户注册事务事实表
尽可能多的丰富一张表的维度信息
too many open files 是hiveserver2 的原因,是unix默认的在运行进程的过程中最多只能打开文件4096个,如果遇到,则需要重启hiveserver2服务,它会关闭一些不用的文件。想要彻底修改,需要更改配置文件。
先划分会话后过滤数据。
十:数仓开发之DWS层
10.1:相关理论(orc列式存储+snappy压缩)
原子指标:业务过程 度量值 聚合逻辑
派生指标:(如果某个需求能通过一个事实表聚合得到就可以被认为是一个派生指标)原子指标 + 统计周期(限制指定业务过程的时间) + 业务限定(除了时间之外其余的限定) + 统计粒度(派生指标结果的行所代表的含义)
衍生指标:一个或多个派生指标的基础上,通过各种逻辑运算符合而成的,比如心率,比例,最近30天的品牌退货率等等。
几乎所有的统计需求都直接或者间接的对应一个或多个派生指标。
可以考虑将这些公共的派生指标保存下来,这样做的主要目的就是减少重复计算,提高数据的复用性。
明确统计指标:
明确统计指标具体的工作是,深入分析需求,构建指标体系。构建指标体系的主要意义就是指标定义标准化。所有指标的定义,都必须遵循同一套标准,这样能有效的避免指标定义存在歧义,指标定义重复等问题。
表的设计思路:一个派生指标放到一个表中,业务过程,统计周期,统计粒度相同的放入到一张表中。
表名中的统计粒度是一个或多个维度
表的结构根据粒度确定,字段根据原子指标确定,一个度量值就是一个字段,不同的聚合逻辑就是一个字段。
DWS层的一张汇总表,只会对应DWD的一张事实表(我们会把业务过程,统计周期,统计粒度相同的放到一个表中,说明一个表中需要的事实是一样的,对应的事实表是一样的,每个事实表,某个事实表属于某个数据域,因此事实表属于哪个数据域,则由它聚合得到的那个汇总表就属于哪个数据域) 即DWS层的数据域是由DWD层聚合得到的。因此,可以根据表名推测出派生指标
比如:
因为DWS层和DWD层的数据域是相同的 业务过程是order,因此可以理解为将该表存储的是
用户最近一天内对订单的相关指标进行的分析,统计。
表名的命名规范:dws_数据域_统计粒度_业务过程_统计周期(1d/nd/td)
注:1d表示最近1日,nd表示最近n日,td表示历史至今。
一个人一天下了多少单的记录
group by (分组字段 聚合函数 常量值)
将原始表中的用户品牌粒度转成一个品牌粒度,必须分组重新聚合
10.1:数仓开发之DWS层 案例
思路分析:
1:构建思维导图,分析是派生指标还是衍生指标,(如果能够通过一个事务事实表聚合得到就是一个派生指标)(事实表是最细粒度,需求的表的最终粒度,我们需要找到共享的中间表的粒度,因此可以从中间粒度的汇总表得到)
2:根据需求找到公共的派生指标
3:设计表结构 行 列 分区:
字段的获取,首先必须有表示粒度的字段,其次,每个派生指标的度量值就是一个字段,最后,设计表的时候要有前瞻性,需要查看DWD层需要聚合的事实表,查看该事实表中所有的度量值地段,并选择作为DWS层的字段;
如果sql中有group by那么select后的字段只能有 分组字段 聚合函数 或是 常量
在hive中count(1)和count(*)是一样的,但是count(*)在字段中有null时与cout(1)和count(*)不一样
版本二的问题:用户人数重复计算(因为按照1d表中的计算聚合得到了最近7天的数据,不正确比如1个用户最近7天每天购买一件,属于一个用户,但是按照版本二的计算逻辑 会是7人)
版本二的问题解决:(调整创建表的结构 降低汇总表的粒度,一个表中维度越多维度越细)
汇总表的公用性是一个重要的指标
因为品类和品牌都是商品的维度属性
DWS层的表是由DWD层的事务表聚合而成的各个粒度的表
10.2:交易域用户商品粒度订单最近1日汇总事实表
因为第一天的数据有历史数据,因此要先进行分区聚合减少数据量之后,然后进行join,dt需要做动态分区字段的值。
因为对应的事实表做了维度退化,因此也必须进行维度退化
用户(注册了的用户)和访客(有没有注册都一样)
日志没有历史数据
union all 连接之后形成一个虚表字段使用第一个表中的字段。
十一 :表
需求分析问题
对业务过程要敏感
nd表不能说明最近7天又访问只能说明最近30日又访问
分组topn over(partition by 按照哪个字段分组 order by 按照哪个字段排名)
如果多个表都可以完成某个需求,那么