知识点结构图
本文初衷是为了学习归纳,若有错误,请指出。
修改记录
时间 | 内容 |
---|---|
2020年9月13日 | 第一次发布 |
推荐书:《数据仓库工具书》(纯理论)、大数据之路-阿里巴巴大数据实践、
数据仓库:英文Data WareHouse,数据仓库是面向主题,为分析数据而设计的,是一个各种数据(包括历史数据和当前数据)的中心存储系统,主要服务于商业智能(也就是BI)和企业决策管理。
商业智能:指用现代数据仓库技术、线上分析处理技术、数据挖掘和数据展现技术进行数据分析以实现商业价值,帮助企业做出明智的业务决策的工具。
面向主题:是在较高的层次能够完整的、统一的刻画某个分析对象所涉及的各项数据和数据间的联系。比如用户订单信息表刻画了用户的购买金额和其他购买行为信息,以及信用卡主档表,记录了信用卡这一业务下用户的卡信息,包含卡号、有效期、额度等。
数据仓库的源系统来源:业务系统数据库、日志采集系统、以及爬虫系统。
日志采集系统:一般是从PC端、APP、IOS对其进行埋点后采集的,也就是采集用户行为。比如上网页版京东搜索“手机”这一行为就会产生行为日志信息,埋点可以记录用户做的所有事情,比如页面浏览、点击、停留、评论、收藏等。
粒度:数据粒度指数据仓库的数据中保存数据的细化程度或综合程度的级别,粒度越小,数据越细,查询范围就越广泛;粒度大就越不够细节。声明粒度意味着精确定义事实表中的一行数据表示什么(比如代表一天的数据、一周的数据还是一个月的数据或者一条订单信息),应该尽可能选择最小粒度,以此来应对各种各样的需求,能满足更多指标开发。
维度:看待事物的角度,
维度表:一般是对事实的描述信息,可以看做是用户分析数据的窗口,包含了事实数据的特性。每一张维度表对应显示世界中的一个对象或者概念,比如用户类型、商品、日期、地区等。
事实表:包含对分析事物的一个或多个度量值(指标)。
指标:可以理解为统计的销售额、转账额等这样的业务统计数。
度量:可以是事实表中存放数值型或者连续次数的字段,例如订单金额、下单次数等。
维度退化:
用户画像:是将用户的每个具体信息抽象成标签,利用这些标签将用户形象具体化,从而为用户提供有针对性的服务,一般是为了后面的推荐系统提供用户标签。
数据质量:在保证数仓正常运行的前提下,对数仓是否运行良好、分析的指标是否正确、数据是否正确进一步检查。
OLAP:是一种软件技术,是分析人员能够迅速、一致、交互地从各个方面观察信息,以达到深入理解数据的目的,从各个方面观察信息,也就是从不同的维度分析数据,因此OLAP也称为多维分析。如下图所示:角度数等于2的N次方减1.
OLAP的类型:ROLAP(Relational OLAP,关系OLAP)和MOLAP(Multi’dimensional OLAP,多维OLAP),还有第三个HOLAP(混合型OLAP)
ROLAP:基于关系型数据库,不需要预计算
MOLAP:基于多维数据集,需要预计算。(Kylin就是一个MOLAP数据引擎)
MOLAP基于多维数据集,一个多维数据集称为一个OLAP Cube。
如下图所示,右方的立体就是一个Cube,而左边的数据在存放到立体时就已经完成了一个聚合计算,每一个小方块代表的应该是多行数据,这里也就是做了预计算
- Cube和Cuboid区别:Cube应该是所有维度组合的一个集合,比如上图具有地区、品类、时间维度的Cube,而Cuboid是细分出来每一个维度组合,如下图的角度1-7:
第一,面向主题:面向主题是指数仓中的数据是按照一定的业务主题划分组织的,能帮助用户做决策和分析探索。
第二,集成性:数仓的数据来源于其他源系统,可以使用各种ETL工具集成汇总到数仓。
第三,稳定性:数仓主要是为了决策分析,它存储大量的历史数据,一般只有新增,没有更新操作;
第四,时变性:时变性是指它具有时间属性,可以不断生成主题的新数据,比如按年月日的增量数据。
数据集市是一种微型的数据仓库,他通常有更少的数据,更少的主题区域,以及更少的历史数据,因此是部门级的,一般只能为某个局部范围内的管理人员服务。(市面上不同公司和书籍对它的概念可能不同,大体来说是这样)
数据仓库是企业级的,能为整个企业各个部门的运行提供决策支持手段。
数据模型是抽象描述现实世界的一种工具和方法,通过抽象的实体及实体之间联系的形式来表示现实世界中事务的相互关系。
数据模型的三要素:数据结构、数据操作、完整性约束。
逻辑数据模型(Logical Data Model)是利用图形方式,采用面向主题的方法、按照3NF的规则有效组织来源多样的各种业务数据 ,通过**数据和关系 **反应业务的一个过程,是进行数据管理、分析和交流的重要手段,同时还能够很好的保证数据的一致性,是实现业务智能(Business Intelligence)的重要基础。
Teradata的逻辑数据模型涵盖以下九大行业:金融服务、医疗卫生、制造业、通讯、娱乐传媒、交通旅游、运输物流、零售、公用事业。
FS-LDM金融数据模型是一个分享成熟稳健的逻辑数据模型和全球性产品,蕴含了现在商业分析决策和客户管理关系的各个方面,利用FS-LDM模型可以直接开展数据仓库模型设计。
FS-LDM主要有以下优势:继承性、灵活性、扩展性、稳定性、可实施性、风险小、应用中性。
一共有十大主题:当事人、渠道、产品、资产、内部机构、地理、协议、事件、营销活动、财务。
第一级是主题、第二级是概念也就是实体、第三级是实体属性和逻辑视图。
建模过程经历系统调研、主题域模型设计、概念模型设计、逻辑模型设计、物理模型设计、模型优化回顾
阿里是4层(ODS、DWD、DWS、ADS),美团是5层(ODS、DWD、DWS、DWT、ADS),京东是9层
ODS层:原始数据层,存放原始数据,直接加载原始日志、数据,数据保持原貌不做处理(同时起到备份的作用)
DWD层:明细数据层,每个地方叫法可能不同,结构和粒度与原始表保持一致,DWD层对ODS层数据进行清洗(去除空值、脏数据、超过极限范围的数据(比如金额出现负值))。
可能会用到的ETL:Hive SQL(hql)、MR、Spark sql、Python、kettle(专门做etl,拖拽+sql,比较注重业务逻辑,但外包比较多)
DWS层:服务数据层,以DWD为基础,按天进行轻度汇总。比如用户行为宽表,记录了用户一天下单数、评论数等。
DWT层:数据主题层,以DWS为基础,按主题进行汇总。比如上面的用户行为宽表是记录了每天的下单数、评论数等,那这里就可以记录用户在今年内下单的总数之类的购买主题,属于某个整体范围。
ADS层:数据应用层,为各种统计报表提供数据。可以从其他层去运算。
从数据源采集时,经过ETL的过程,然后在数仓中要经过分层,可以比喻成TCPip协议,分成多层,每一层处理一件事。为公司决策,提供数据支持的。
(1)把复杂问题简单化:将复杂的任务分解成多层来完成,每一层只处理简单的任务,方便定位问题。
(2)减少重复开发:规范数据分层,通过中间层数据能够减少极大的重复计算,增加计算结果的复用性。
(3)隔离原始数据:不论是数据的异常还是数据的敏感性,使真实数据与统计数据解耦开。比如前面说的ODS层数据更像是备份数据,那么后面的数据层就可以根据需要随意统计数据,其他层发生异常,只要ODS层的数据还在就能取到原始数据。
-ODS层命名为ods_表名
-DWD层命名为dwd_dim/fact_表名(维度表和事实表)
-DWS层命名为dws_表名
-DWT层命名为dwt_表名
-ADS层命名为ads_表名
-临时表命名为xxx_tmp
-用户行为表,以log为后缀
数据源 _to _目标 _db/log.sh (这里是蛇形命名法,另一种就是驼峰命名法)
用户行为脚本以log为后缀;业务数据脚本以db为后缀。
范式可以理解为设计一张数据表的表结构,符合标准的级别(其实就是规范和要求)。
关系型数据库设计时,遵照一定的规范要求,目的在于降低数据的冗余性。
为什么要降低数据冗余性?
(1)十几年前,磁盘很贵,为了减少磁盘存储
(2)以前没有分布式系统,都是单机,只能增加磁盘,磁盘个数也是有限的。
(3)一次修改,要修改很多张表,很难保证数据一致性。
范式的缺点是获取数据时,需要通过Join拼接出最后的数据。
目前业界范式有:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯科德范式(BCNF)、第四范式(4NF)、第五范式(5NF).(企业追求主要前面三种)
比如通过(学号,课程)可以推出分数,但是单独用学号推断不出分数,那么就说:分数完全依赖于(学号,课程)。
即AB能得出C,但是AB单独得不出C,那么说C完全依赖于AB。
比如通过(学号,课程)可以推出姓名,但其实可以直接通过学号退出姓名,那么就说:姓名部分依赖于(学号,课程);
即通过AB能得出C,通过A也能得出C,或者通过B也能得出C,那么说C部分依赖于AB。
比如学号推出系名,系名推出系主任,但是系主任推不出学号,系主任主要依赖于系名,这种可以说:系主任传递依赖于学号。
通过A得到B,通过B得到C,但是通过C不能得到A,那么说C传递依赖于A。
第一范式1NF的核心就是属性不可切割。1NF是所有关系型数据库的最基本要求,如果数据表的设计不符合这个最基本的要求,那么操作一定是不能成功的。
第二范式2NF的核心是不能存在“部分依赖”
即在1NF的基础上,消除非主属性对主键的部分依赖
比如有表(学号,课程,姓名,系名,分数)是不符合的。因为姓名可以直接有学号得出,而不是(学号,课程)这一组合,需要改成(学号,课程,分数)和(学号,姓名,系名)
第三范式3NF的核心是不能存在传递依赖
即在1NF的基础上,每个非主属性都不部分依赖于主键也不存在传递依赖
当今的数据处理大致可以分成两大类:联机事务处理 OLTP(on-line transaction processing)、联机分析处理 OLAP(On-Line Analytical Processing)。OLTP 是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。OLAP 是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。
区别:(待完善)
对比属性 | OLTP | OLAP |
---|---|---|
读特性 | 每次查询只返回少量记录 | 对大量记录进行汇总 |
写特性 | 随机、低延时写入用户的输入 | 批量导入 |
使用场景 | 用户,Java EE 项目 | 内部分析师,为决策提供支持 |
数据表征 | 最新数据状态 | 随时间变化的历史状态 |
数据规模 | GB | TB 到 PB |
关系模型如图所示,严格遵循第三范式(3NF),从图中可以看出,较为松散、零碎, 物理表数量多,而数据冗余程度低,由于数据分布于众多的表中,这些数据可以更为灵活地被应用,功能性较强。关系模型主要应用与 OLTP 系统中,为了保证数据的一致性以及避免冗余,所以大部分业务系统的表都是遵循第三范式的。
维度模型如图所示,主要应用于 OLAP 系统中,通常以某一个事实表为中心进行表的组织,主要面向业务,特征是可能存在数据的冗余,但是能方便的得到数据。
关系模型虽然冗余少,但是在大规模数据,跨表分析统计查询过程中,会造成多表关联,这会大大降低执行效率。所以通常我们采用维度模型建模,把相关各种表整理成两种:事实表和维度表两种。
在维度建模的基础上又分为三种模型:星型模型、雪花模型、星座模型。
雪花模型和星型模型的区别主要在于维度的层级,标准的星型模型维度只有一层,而雪花模型可能会涉及多级。
雪花模型比较靠近3NF,但是无法完全遵守,因为遵循3NF的性能成本太高,主要是join操作。
星型模型追求一级维度,避免多级join操作。
星座模型与前面两种情况的区别是事实表的数量,星座模型是基于多个事实表,基本上是很多数据仓库的常态,因为很多数据仓库都是多个事实表,所以星座只反映是否有多个事实表,他们之间是否共享一些维度表。
通常情况下,星座模型是星型模型或雪花模型发展到业务后期的表现形式。
模型的选择:首先就是星座不星座这个只跟数据和需求有关系,跟设计没有关系,不用选择。星型还是雪花,取决于性能优先(星型),还是灵活优先(雪花)。
目前实际企业开发中,不会绝对只选择一种,根据情况灵活配置,甚至并存(一层维度和多层维度都保存),但是从整体看,更倾向于维度更少的星型模型,尤其是Hadoop体系,减少Join就是减少Shuffle,性能差距很大。(关系型数据可以依靠强大的主键索引)
(1)维度表:一般是对事实的描述信息,每一张维度表对应显示世界中的一个对象或者概念,比如用户类型、商品、日期、地区等。
维表的特征:
A.维表的范围很宽(具有多个属性、列比较多)
B.跟事实表相比,行数相对较小,通常小于10万条
C.内容相对固定:编码表。
(2)事实表:事实表中的每一行数据都代表一个业务事件(下单、支付、退款、评价等),“事实”这个术语表示业务事件的度量值(可统计次数、个数、金额等),例如订单事件中的下单金额。
订单表、支付表、详情表、收藏表等,可以看出一般是有动词。
每一个事实表的行包括:具有可加性的数值型的度量值、与维表相连接的外键、通常具有两个和两个以上的外键、外键之间表示维表之间多对多的关系。但很少有关联事实表的外键。
事实表的特征:
--非常的大
--内容相对的窄
--经常发生变化,每天会新增很多。
(3)事实表的分类:
A.事务型事实表
以每个事务或事件为单位,例如一个销售订单记录,一笔支付记录等,作为事实表里的一行数据。一旦事务被提交,事实表数据被插入,数据就不再进行更改,其更新方式为增量
更新。
B.周期型快照事实表
周期型快照事实表中不会保留所有数据,只保留固定时间间隔的数据,例如每天或者每月的销售额,或每月的账户余额等。全量
C.累计型快照事实表
累计快照事实表用于跟踪业务事实的变化。例如,数据仓库中可能需要累积或者存储订单从下订单开始,到订单商品被打包、运输、和签收的各个业务阶段的时间点数据来跟踪订单声明周期的进展情况。当这个业务过程进行时,事实表的记录也要不断更新。
(old + new + full outer join)
主要过程(自己归纳,可能部分有误):
(1)保持数据原貌不做任何修改,起到备份数据的作用
(2)数据采用压缩,减少磁盘存储空间(例如:原始数据 100G,可以压缩到 10G 左
右,压缩多少取决于数据格式、嵌套层级、采用的压缩算法)
(3)创建分区表,防止后续的全表扫描
DWD 层需构建维度模型,一般采用星型模型,呈现的状态一般为星座模型。
维度建模一般按照以下四个步骤:(可能面试会问,必须要会答)
选择业务过程→声明粒度→确认维度→确认事实(度量值)
(1)选择业务过程
在业务系统中,挑选我们感兴趣的业务线,比如下单业务,支付业务,退款业务,物流
业务,一条业务线对应一张事实表。
(2)声明粒度
数据粒度指数据仓库的数据中保存数据的细化程度或综合程度的级别。声明粒度意味着精确定义事实表中的一行数据表示什么(比如代表一天的数据、一周的数据还是一个月的数据或者一条订单信息),应该尽可能选择最小粒度,以此来应对各种各样的需求,能满足更多指标开发。
典型的粒度声明如下:
订单中,每个商品项作为下单事实表中的一行,粒度为每次下单
每周的订单次数作为一行,粒度就是每周下单。
每月的订单次数作为一行,粒度就是每月下单
(3)确认维度
维度的主要作用是描述业务的事实,主要表示的是“谁,何处,何时”等信息。对应下图表格。
(4)确认事实
此处的“事实”一词,指的是业务中的度量值,例如订单金额、下单次数等。
在 DWD 层,以**业务过程为建模驱动,基于每个具体业务过程的特点,构建最细粒度**的明细层事实表。事实表可做适当的宽表化处理。
维度退化,类似于将跟商品有关的码表,都放到一张商品维度表中使用,减少后续join操作。
至此,数仓的维度建模已经完毕,DWS、DWT 和 ADS 和维度建模已经没有关系了。
DWS 和 DWT 都是建宽表,宽表都是按照主题去建。主题相当于观察问题的角度。对
应着维度表
统计各个主题对象的当天行为,服务于 DWT 层的主题宽表,以及一些业务明细数据, 应对特殊需求(例如,购买行为,统计商品复购率)
以分析的主题对象为建模驱动,基于上层的应用和产品的指标需求,构建主题对象的全 量宽表。
对系统各大主题指标分别进行分析。比如流失用户数、最近连续三周活跃用户、留存率、商品销量排行等。
ODS层:(备份工作)
(1)保持数据原貌不做任何修改,起到备份作用。
(2)创建分区表,防止后续的全表扫描
(3)采用lzo压缩,并创建索引(切片)
(4)创建外部表(多人共用)
DWD层:(负责准备工作)
(1)数仓维度建模(主要是星型模型) = > 维度退化,好处:减少后续大量join操作
(2)数据清洗
(3)采用lzo压缩
(4)parquet列式存储
(5)脱敏(手机号、身份证好、个人信息等)
(6)对用户行为数据,进行解析,比如ods层存的是大字段或者json格式的数据。
DWS层:每天各个主题的行为数据,会站在维度的角度分析。
DWT层:从开始创建,一直到现在的累积数据
ADS层:分析具体报表:给老板看的,直观数据。(日活数据,销售数据)
(1)保持数据原貌不做任何修改,起到备份数据的作用
(2)数据采用 LZO 压缩,减少磁盘存储空间。100G 数据可以压缩到 10G 以内。
(3)创建分区表,防止后续的全表扫描,在企业开发中大量使用分区表。
(4)创建外部表。在企业开发中,除了自己用的临时表,创建内部表外,绝大多数场景
都是创建外部表。
内部表和外部表的区别:
删除内部表时会把元数据和原始数据都删除;外部表删除时只把元数据删除,原始数据保留。
什么场景创建外部表、内部表?
多人使用的表通常都是外部表,在企业大量使用外部表。内部表:公司自己使用的临时表,才会创建内部表。
(1)启动hive
[atguigu@hadoop102 hive]$ nohup bin/hive --service metastore &
[atguigu@hadoop102 hive]$ nohup bin/hive --service hiveserver2 &
[atguigu@hadoop102 hive]$ bin/hive
(2)显示数据库
hive (default)> show databases;
(3)创建数据库
hive (default)> create database gmall;
(4)使用数据库
hive (default)> use gmall;
企业可能一层创建一个数据库,也可能只创建一个数据库包含多层在内。
以电商数据为例,分为启动日志和事件日志,启动日志数据比较简单,事件日志比较复杂,但在ODS层是保留原样的数据,所以是直接拉取存放即可,只建一个字段来存放,而数据的拆分清洗放在DWD层去做。
(1)创建外部表:创建输入数据是 lzo ,输出是 text,支持 json 解析的分区表。
hive (gmall)> drop table if exists ods_start_log; --标准写法,建表前先判断是否存在
CREATE EXTERNAL TABLE ods_start_log (`line` string) --像前面说的ods原样保存,只需建一个字段存数据。注意这里line有加``号,是为了防止定义的字段名称是关键字也能生效。
PARTITIONED BY (`dt` string)
STORED AS
INPUTFORMAT 'com.hadoop.mapred.DeprecatedLzoTextInputFormat' --指定数据输入格式是lzo
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' --指定输出格式是txt
LOCATION '/warehouse/gmall/ods/ods_start_log'; --数据存放位置,创建完后就会有这个路径了
(2)加载数据
hive (gmall)> load data inpath '/origin_data/gmall/log/topic_start/2020-03-10' into table gmall.ods_start_log partition(dt='2020-03-10'); ----inpath是集群路径,localpath是本地路径
--加载完后,原来inpath的文件夹就消失了。
注意:时间格式都配置成 YYYY-MM-DD 格式,这是 Hive 默认支持的时间格式
(3)查看是否加载成功
hive (gmall)> select * from ods_start_log where dt='2020-03-10' limit 2;
(4)为lzo文件创建索引
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20. jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_start_log/dt=2020-03-10
事件日志的数据比较复杂,但在ods层还是原样保存。
过程和上面创建启动日志的表一致:
hive (gmall)> drop table if exists ods_event_log;
CREATE EXTERNAL TABLE ods_event_log(`line` string)
PARTITIONED BY (`dt` string)
STORED AS INPUTFORMAT 'com.hadoop.mapred.DeprecatedLzoTextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION '/warehouse/gmall/ods/ods_event_log';
hive (gmall)> load data inpath '/origin_data/gmall/log/topic_event/2020-03-10'
into table gmall.ods_event_log partition(dt='2020-03-10');
hive (gmall)> select * from ods_event_log where dt="2020-03-10" limit 2;
[atguigu@hadoop102 hadoop-2.7.2]$ hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20. jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_event_log/dt=2020-03-10
(1)在 hadoop102 的/home/atguigu/bin 目录下创建脚本
[atguigu@hadoop102 bin]$ vim hdfs_to_ods_log.sh
(2)在脚本中编写如下内容:
#!/bin/bash
# 定义变量方便修改
APP=gmall --这里定义要插入的数据库
hive=/opt/module/hive/bin/hive --hive安装的路径+启动hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F` --获取T+1日日期标准格式的写法
fi
echo "===日志日期为 $do_date==="
sql="
load data inpath '/origin_data/gmall/log/topic_start/$do_date' overwrite
into table ${APP}.ods_start_log partition(dt='$do_date'); --注意这里的$app变量
load data inpath '/origin_data/gmall/log/topic_event/$do_date' overwrite
into table ${APP}.ods_event_log partition(dt='$do_date');
"
$hive -e "$sql"
#为表建索引
hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_start_log/dt=$do_date --+ -e直接执行
hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_event_log/dt=$do_date
[ -n 变量值 ] 判断变量的值,是否为空
(3)增加脚本执行权限
[atguigu@hadoop102 bin]$ chmod 777 hdfs_to_ods_log.sh
(4)脚本使用
[atguigu@hadoop102 module]$ hdfs_to_ods_log.sh 2020-03-11
(5)查看导入数据
hive (gmall)> select * from ods_start_log where dt='2020-03-11' limit 2;
select * from ods_event_log where dt='2020-03-11' limit 2;
(6)执行时间:企业开发中一般在每日凌晨 30 分~1 点(凌晨30分之前,上游的数据可能还没处理完。)
这里加载的数据和上面的用户行为数据不同,用户行为数据主要是json格式的数据,只需要一个字段存放,后期在DWD层做拆分清洗,而业务数据具有多张表,每张表多个字段。
(1)以订单表为例的建表语句:
hive (gmall)> drop table if exists ods_order_info;
create external table ods_order_info (
`id` string COMMENT '订单号',
`final_total_amount` decimal(10,2) COMMENT '订单金额',
`order_status` string COMMENT '订单状态',
`user_id` string COMMENT '用户 id',
`out_trade_no` string COMMENT '支付流水号',
`create_time` string COMMENT '创建时间',
`operate_time` string COMMENT '操作时间',
`province_id` string COMMENT '省份 ID',
`benefit_reduce_amount` decimal(10,2) COMMENT '优惠金额',
`original_total_amount` decimal(10,2) COMMENT '原价金额',
`feight_fee` decimal(10,2) COMMENT '运费'
) COMMENT '订单表'
PARTITIONED BY (`dt` string)
row format delimited fields terminated by '\t' --分隔符,与原始文件设置的分隔符对应
STORED AS
INPUTFORMAT 'com.hadoop.mapred.DeprecatedLzoTextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
location '/warehouse/gmall/ods/ods_order_info/';
(2)ODS层加载业务数据脚本
[atguigu@hadoop102 bin]$ vim hdfs_to_ods_db.sh
添加如下内容:
#!/bin/bash
APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$2" ] ;then
do_date=$2
else
do_date=`date -d "-1 day" +%F`
fi
#遇到日期的改成$do_date,遇到表的改成${APP}
sql1="
load data inpath '/origin_data/$APP/db/order_info/$do_date' OVERWRITE into table
${APP}.ods_order_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/order_detail/$do_date' OVERWRITE into table
${APP}.ods_order_detail partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/sku_info/$do_date' OVERWRITE into table
${APP}.ods_sku_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/user_info/$do_date' OVERWRITE into table
${APP}.ods_user_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/payment_info/$do_date' OVERWRITE into table
${APP}.ods_payment_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/base_category1/$do_date' OVERWRITE into table
${APP}.ods_base_category1 partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/base_category2/$do_date' OVERWRITE into table
${APP}.ods_base_category2 partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/base_category3/$do_date' OVERWRITE into table
${APP}.ods_base_category3 partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/base_trademark/$do_date' OVERWRITE into table
${APP}.ods_base_trademark partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/activity_info/$do_date' OVERWRITE into table
${APP}.ods_activity_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/activity_order/$do_date' OVERWRITE into table
${APP}.ods_activity_order partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/cart_info/$do_date' OVERWRITE into table
${APP}.ods_cart_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/comment_info/$do_date' OVERWRITE into table
${APP}.ods_comment_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/coupon_info/$do_date' OVERWRITE into table
${APP}.ods_coupon_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/coupon_use/$do_date' OVERWRITE into table
${APP}.ods_coupon_use partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/favor_info/$do_date' OVERWRITE into table
${APP}.ods_favor_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/order_refund_info/$do_date' OVERWRITE into table
${APP}.ods_order_refund_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/order_status_log/$do_date' OVERWRITE into table
${APP}.ods_order_status_log partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/spu_info/$do_date' OVERWRITE into table
${APP}.ods_spu_info partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/activity_rule/$do_date' OVERWRITE into table
${APP}.ods_activity_rule partition(dt='$do_date');
load data inpath '/origin_data/$APP/db/base_dic/$do_date' OVERWRITE into table
${APP}.ods_base_dic partition(dt='$do_date');
"
sql2="
load data inpath '/origin_data/$APP/db/base_province/$do_date' OVERWRITE into table
${APP}.ods_base_province;
load data inpath '/origin_data/$APP/db/base_region/$do_date' OVERWRITE into table
${APP}.ods_base_region;
"
case $1 in
"first"){
$hive -e "$sql1"
$hive -e "$sql2"
};;
"all"){
$hive -e "$sql1"
};;
esac
#这里不用为表建索引,可以到原始数据的目录看下是否已经建过了,如果有lzo.index的索引文件了,那么在上面load的时候就会直接把索引文件导过去,就没有必要创建索引,除非是原始目录那里本身也没有。
#这里的业务数据的索引是在从mysql到hdfs时sqoop加了那两个参数:compress和compression-codec lzop建立的。
[atguigu@hadoop102 bin]$ chmod 777 hdfs_to_ods_db.sh
[atguigu@hadoop102 bin]$ hdfs_to_ods_db.sh first 2020-03-10
[atguigu@hadoop102 bin]$ hdfs_to_ods_db.sh all 2020-03-11
hive (gmall)> select * from ods_order_detail where dt='2020-03-11';
注意:这里有建增量表和全量表,但建表语句和导入语句里面没有区分,是因为sqoop那里做了加工分别取出每日的增量数据和全量数据,所以到ODS层存的是每日的增量数据和每日的全量数据。(个人理解)
对用户行为数据解析
对核心数据进行判空过滤。
对业务数据采用维度模型重新建模,即维度退化。
hive (gmall)> drop table if exists dwd_start_log;
CREATE EXTERNAL TABLE dwd_start_log(
`mid_id` string,
`user_id` string,
`version_code` string,
`version_name` string,
`lang` string,
`source` string,
`os` string,
`area` string,
`model` string,
`brand` string,
`sdk_version` string,
`gmail` string,
`height_width` string,
`app_time` string,
`network` string,
`lng` string,
`lat` string,
`entry` string,
`open_ad_type` string,
`action` string,
`loading_time` string,
`detail` string,
`extend1` string )
PARTITIONED BY (dt string)
stored as parquet --以这种方式存储,有更快读取方式,另一种常用的是orc
location '/warehouse/gmall/dwd/dwd_start_log/'
TBLPROPERTIES('parquet.compression'='lzo');
说明:数据采用 parquet 存储方式,是可以支持切片的,不需要再对数据创建索引。
系统内部函数,用法如下:
$符代表一行内容。
#现有json数据:
Xjson=[{"name":" 大 郎 ","sex":" 男 ","age":"25"},{"name":" 西 门 庆 ","sex":" 男 ","age":"47"}]
#取出第一个json对象:
SELECT get_json_object(xjson,"$.[0]") FROM person;
--结果:{"name":"大郎","sex":"男","age":"25"}
#取出第一个json对象的age字段的值:
SELECT get_json_object(xjson,"$.[0].age") FROM person;
--结果是:5
hive (gmall)> insert overwrite table dwd_start_log
PARTITION (dt='2020-03-10')
select
get_json_object(line,'$.mid') mid_id,
get_json_object(line,'$.uid') user_id,
get_json_object(line,'$.vc') version_code,
get_json_object(line,'$.vn') version_name,
get_json_object(line,'$.l') lang,
get_json_object(line,'$.sr') source,
get_json_object(line,'$.os') os,
get_json_object(line,'$.ar') area,
get_json_object(line,'$.md') model,
get_json_object(line,'$.ba') brand,
get_json_object(line,'$.sv') sdk_version,
get_json_object(line,'$.g') gmail,
get_json_object(line,'$.hw') height_width,
get_json_object(line,'$.t') app_time,
get_json_object(line,'$.nw') network,
get_json_object(line,'$.ln') lng,
get_json_object(line,'$.la') lat,
get_json_object(line,'$.entry') entry,
get_json_object(line,'$.open_ad_type') open_ad_type,
get_json_object(line,'$.action') action,
get_json_object(line,'$.loading_time') loading_time,
get_json_object(line,'$.detail') detail,
get_json_object(line,'$.extend1') extend1
from ods_start_log
where dt='2020-03-10';
hive (gmall)> select * from dwd_start_log where dt='2020-03-10' limit 2;
#!/bin/bash
# 定义变量方便修改
APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
set hive.exec.dynamic.partition.mode=nonstrict; --开启非严格模式,使用动态分区
insert overwrite table "$APP".dwd_start_log
PARTITION (dt='$do_date')
select
get_json_object(line,'$.mid') mid_id,
get_json_object(line,'$.uid') user_id,
get_json_object(line,'$.vc') version_code,
get_json_object(line,'$.vn') version_name,
get_json_object(line,'$.l') lang,
get_json_object(line,'$.sr') source,
get_json_object(line,'$.os') os,
get_json_object(line,'$.ar') area,
get_json_object(line,'$.md') model,
get_json_object(line,'$.ba') brand,
get_json_object(line,'$.sv') sdk_version,
get_json_object(line,'$.g') gmail,
get_json_object(line,'$.hw') height_width,
get_json_object(line,'$.t') app_time,
get_json_object(line,'$.nw') network,
get_json_object(line,'$.ln') lng,
get_json_object(line,'$.la') lat,
get_json_object(line,'$.entry') entry,
get_json_object(line,'$.open_ad_type') open_ad_type,
get_json_object(line,'$.action') action,
get_json_object(line,'$.loading_time') loading_time,
get_json_object(line,'$.detail') detail,
get_json_object(line,'$.extend1') extend1
from "$APP".ods_start_log
where dt='$do_date';
"
$hive -e "$sql"
这里以电商的用户行为表举例,这张表中有公共字段的数据,然后下面是各个{}概括起来的事件信息,结构如第二张图和第三张图,它的事件信息可以拆分成不同的商品点击表、评论表、收藏表等。这类json数据比较复杂,需要用到自定义UDF和UDTF处理。
明细表用于存储ODS层原始表转换过来的明细数据,也是作为一张中间表,后续再对json格式数据进一步处理。
如下图的中间表dwd_base_event_log,主要取出公共字段,而对于event字段都存储在event_json字段中。
event_name是一个事件的标志。
#创建事件日志基础明细表
hive (gmall)> drop table if exists dwd_base_event_log;
CREATE EXTERNAL TABLE dwd_base_event_log(
`mid_id` string,
`user_id` string,
`version_code` string,
`version_name` string,
`lang` string,
`source` string,
`os` string,
`area` string,
`model` string,
`brand` string,
`sdk_version` string,
`gmail` string,
`height_width` string,
`app_time` string,
`network` string,
`lng` string,
`lat` string,
`event_name` string,
`event_json` string,
`server_time` string)
PARTITIONED BY (`dt` string)
stored as parquet
location '/warehouse/gmall/dwd/dwd_base_event_log/'
TBLPROPERTIES('parquet.compression'='lzo');
说明:其中 event_name 和 event_json 用来对应事件名和整个事件。这个地方将原始日志1 对多的形式拆分出来了。操作的时候我们需要将原始日志展平,需要用到 UDF 和 UDTF。
UDF特点:一行进一行出。简称,一进一出。
大概思路如BaseFieldUDF().evaluate(line,“mid”),先获取一层key value对,如果key是et,那么把对应的json数据当做value,然后继续对这个value做key value判断。
步骤:
1)创建一个 maven 工程:hivefunction
2)创建包名:com.atguigu.udf
3)在 pom.xml 文件中添加如下内容
<properties>
<hive.version>2.3.0hive.version>
properties>
<dependencies>
<-- 添加hive依赖-->
<dependency>
<groupId>org.apache.hivegroupId>
<artifactId>hive-execartifactId>
<version>${hive.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-pluginartifactId>
<version>2.3.2version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<artifactId>maven-assembly-pluginartifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
configuration>
<executions>
<execution>
<id>make-assemblyid>
<phase>packagephase>
<goals>
<goal>singlegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
注意 1:如果 hive 的 jar 包下载失败,可以将如下参数配置添加到 idea 中
-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
-Dmaven.wagon.http.ssl.ignore.validity.dates=true
详见:https://blog.csdn.net/qq_22041375/article/details/103491941
注意 2:如果提示 pentaho-aggdesigner-algorithm.jar 包下载失败(原因是这个jar包阿里云是指向一个中间仓库,但实际上这个包在另一个spring仓库中),需要在 maven 的 pom 中增加如下仓库:
<repositories>
<repository>
<id>spring-pluginid>
<url>https://repo.spring.io/plugins-release/url>
repository>
repositories>
判断包实际的仓库位置,需要上官网查找,如:https://mvnrepository.com/artifact/org.pentaho/pentaho-aggdesigner-algorithm/5.1.5-jhyde,复制jar包完整名称,
**注意3:**如果出现 idea 内存溢出
Exception in thread “main” java.lang.StackOverflowError
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
解决办法:把-Xmx512 -Xms128m -Xss2m 添加到下图位置。
4)BaseFieldUDF代码
注意:使用 main 函数主要用于模拟数据测试。
UDTF特点:多行进多行出。 简称,多进多出。
设计思路如下图所示:
1.定义UDTF,集成自GenericUDTF,重写三个方法:initialize,process,close.
2.initialize方法中指定输出参数的名词和参数类型
3.process方法中,传入json 数组,从前面UDF传入et而来,输入一条记录,输出若干条记录。
然后对et遍历,先获取每一个en_name,然后再把每一个整体赋给result
1)创建包名:com.atguigu.udtf (在同一项目下创建)
2)在 com.atguigu.udtf 包下创建类名:EventJsonUDTF
3)用于展开业务字段
4)打包
5)将 hivefunction-1.0-SNAPSHOT.jar 上传到 hadoop102 的/opt/module,然后再将该 jar 包上
传到 HDFS 的/user/hive/jars 路径下
[atguigu@hadoop102 module]$ hadoop fs -mkdir -p /user/hive/jars
[atguigu@hadoop102 module]$ hadoop fs -put hivefunction-1.0-SNAPSHOT.jar /user/hive/jars
6)创建永久函数与开发好的 java class 关联
hive (gmall)> create function base_analizer as 'com.atguigu.udf.BaseFieldUDF'
using jar 'hdfs://hadoop102:9000/user/hive/jars/hivefunction-1.0-SNAPSHO
T.jar';
create function flat_analizer as 'com.atguigu.udtf.EventJsonUDTF' using jar 'hdfs://hadoop102:9000/user/hive/jars/hivefunction-1.0-SNAPSHO T.jar';
7)注意:如果修改了自定义函数重新生成 jar 包怎么处理?只需要替换 HDFS 路径上的旧 jar 包,然后重启 Hive 客户端即可。
hive (gmall)> insert overwrite table dwd_base_event_log partition(dt='2020-03-10')
select
base_analizer(line,'mid') as mid_id,
base_analizer(line,'uid') as user_id,
base_analizer(line,'vc') as version_code,
base_analizer(line,'vn') as version_name,
base_analizer(line,'l') as lang,
base_analizer(line,'sr') as source,
base_analizer(line,'os') as os,
base_analizer(line,'ar') as area,
base_analizer(line,'md') as model,
base_analizer(line,'ba') as brand,
base_analizer(line,'sv') as sdk_version,
base_analizer(line,'g') as gmail,
base_analizer(line,'hw') as height_width,
base_analizer(line,'t') as app_time,
base_analizer(line,'nw') as network,
base_analizer(line,'ln') as lng,
base_analizer(line,'la') as lat,
event_name,
event_json,
base_analizer(line,'st') as server_time
from ods_event_log lateral view flat_analizer(base_analizer(line,'et')) tmp_flat as
event_name,event_json
where dt='2020-03-10' and base_analizer(line,'et')<>'';
这里最后UDTF的用法有所不同,用lateral view引入flat_analizer函数,把base_analizer的每一行中的et这个json当做参数传入,输出en_name和en_json。
[atguigu@hadoop102 bin]$ vim ods_to_dwd_base_log.sh
#!/bin/bash
# 定义变量方便修改
APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
use gmall;
insert overwrite table "$APP".dwd_base_event_log partition(dt='$do_date')
select
base_analizer(line,'mid') as mid_id,
base_analizer(line,'uid') as user_id,
base_analizer(line,'vc') as version_code,
base_analizer(line,'vn') as version_name,
base_analizer(line,'l') as lang,
base_analizer(line,'sr') as source,
base_analizer(line,'os') as os,
base_analizer(line,'ar') as area,
base_analizer(line,'md') as model,
base_analizer(line,'ba') as brand,
base_analizer(line,'sv') as sdk_version,
base_analizer(line,'g') as gmail,
base_analizer(line,'hw') as height_width,
base_analizer(line,'t') as app_time,
base_analizer(line,'nw') as network,
base_analizer(line,'ln') as lng,
base_analizer(line,'la') as lat,
event_name,
event_json,
base_analizer(line,'st') as server_time
from "$APP".ods_event_log lateral view flat_analizer(base_analizer(line,'et')) tem_flat as
event_name,event_json
where dt='$do_date' and base_analizer(line,'et')<>'';
"
$hive -e "$sql"
注意:使用自定义函数时,需要在执行脚本前,增加上要使用的库。例如:“use gmall;” ,就在sql中,因为自定义的函数是在gmall的,而刚进去的hive库是default,需要手动指定使用的数据库
另一点注意base_analizer(line,‘st’) 中st要用单引号,平时可以用双引号,但在这里因为有sql="…",函数里面用双引号会被当做是sql=""的结尾而报错
[atguigu@hadoop102 bin]$ chmod 777 ods_to_dwd_base_log.sh
[atguigu@hadoop102 module]$ ods_to_dwd_base_log.sh 2020-03-11
hive (gmall)> select * from dwd_base_event_log where dt='2020-03-11' limit 2;
这里要做的事情就是对基础信息表的event_json字段做进一步解析,解析成不同的字段,可以直接用hive的内置函数get_json_object()
以商品点击表为例:
#建表语句
hive (gmall)> drop table if exists dwd_display_log;
CREATE EXTERNAL TABLE dwd_display_log(
`mid_id` string,
`user_id` string,
`version_code` string,
`version_name` string,
`lang` string,
`source` string,
`os` string,
`area` string,
`model` string,
`brand` string,
`sdk_version` string,
`gmail` string,
`height_width` string,
`app_time` string,
`network` string,
`lng` string,
`lat` string,
`action` string,
`goodsid` string,
`place` string,
`extend1` string,
`category` string,
`server_time` string
)
PARTITIONED BY (dt string)
stored as parquet
location '/warehouse/gmall/dwd/dwd_display_log/'
TBLPROPERTIES('parquet.compression'='lzo');
#导入数据
hive (gmall)>
insert overwrite table dwd_display_log PARTITION (dt='2020-03-10')
select
mid_id,
user_id,
version_code,
version_name,
lang,
source,
os,
area,
model,
brand,
sdk_version,
gmail,
height_width,
app_time,
network,
lng,
lat,
get_json_object(event_json,'$.kv.action') action,
get_json_object(event_json,'$.kv.goodsid') goodsid,
get_json_object(event_json,'$.kv.place') place,
get_json_object(event_json,'$.kv.extend1') extend1,
get_json_object(event_json,'$.kv.category') category,
server_time
from dwd_base_event_log
where dt='2020-03-10' and event_name='display';
注意:表名取自上一步加工过的dwd_base_event_log,event_name取每一个具体表的事件类型。(整体是用一个中间表来存放多层json格式数据,然后再对中间表简化处理)
这里举例两类event,再有多的event直接加在sql中即可。
[atguigu@hadoop102 bin]$ vim ods_to_dwd_event_log.sh
#!/bin/bash
#定义变量方便修改
APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
insert overwrite table "$APP".dwd_display_log
PARTITION (dt='$do_date')
select
mid_id,
user_id,
version_code,
version_name,
lang,
source,
os,
area,
model,
brand,
sdk_version,
gmail,
height_width,
app_time,
network,
lng,
lat,
get_json_object(event_json,'$.kv.action') action,
get_json_object(event_json,'$.kv.goodsid') goodsid,
get_json_object(event_json,'$.kv.place') place,
get_json_object(event_json,'$.kv.extend1') extend1,
get_json_object(event_json,'$.kv.category') category,
server_time
from "$APP".dwd_base_event_log
where dt='$do_date' and event_name='display';
insert overwrite table "$APP".dwd_newsdetail_log
PARTITION (dt='$do_date')
select
mid_id,
user_id,
version_code,
version_name,
lang,
source,
os,
area,
model,
brand,
sdk_version,
gmail,
height_width,
app_time,
network,
lng,
lat,
get_json_object(event_json,'$.kv.entry') entry,
get_json_object(event_json,'$.kv.action') action, get_json_object(event_json,'$.kv.goodsid') goodsid, get_json_object(event_json,'$.kv.showtype') showtype, get_json_object(event_json,'$.kv.news_staytime') news_staytime, get_json_object(event_json,'$.kv.loading_time') loading_time, get_json_object(event_json,'$.kv.type1') type1,
get_json_object(event_json,'$.kv.category') category,
server_time
from "$APP".dwd_base_event_log
where dt='$do_date' and event_name='newsdetail';
"
$hive -e "$sql"
[atguigu@hadoop102 bin]$ chmod 777 ods_to_dwd_event_log.sh
[atguigu@hadoop102 module]$ ods_to_dwd_event_log.sh 2020-03-11
hive (gmall)> select * from dwd_comment_log where dt='2020-03-11' limit 2;
建表指定存储位置 + 插入数据,插入数据过程中需要join 2.3DWD层用户行为事件表解析出来的明细数据,也是多张表的维度退化成一张表的维度的一个过程。
其中维度表可以参考下面表的建表字段,可以发现都是一些对事物的描述性词以及列比较多,越详细属性列就越多,而事实表存储的是id和度量值。
订单明细事实表(事务性快照事实表)
外键id和度量值比较多。
假如面试官问事实表怎么建的,可以回答 外键 + 度量值
加购事实表(周期型快照事实表,这里是每日快照,也可以每月、每周)
由于购物车的数量是会发生变化,所以导增量不合适。 每天做一次快照,导入的数据是全量,区别于事务型事实表是每天导入新增。
周期型快照事实表劣势:存储的数据量会比较大。
解决方案:周期型快照事实表存储的数据比较讲究时效性,时间太久了的意义不大,可以删除以前的数据。>
个人理解企业大部分还是选择这种方式来存储的,而不是事务型事实表,因为不能确保数据不会变动。
优惠券领用事实表(累计型快照事实表1,要学会)
累计型快照事实表用于跟踪业务事实的变化。例如记录商品从下单、打包到发货、收货的信息。
优惠卷的生命周期:领取优惠卷-》用优惠卷下单-》优惠卷参与支付
注意,累计型快照事实表因为是要跟踪业务事实的变化,所以假如前面某分区的数据发生变化,这变化的数据也是要更新到前面分区的,而不是只在最新分区体现业务事实变化而已。
比如这里的优惠券领用事实表:
(1)首先3-08号领了一张券,3-09领了5张券都没有使用,到了3-10号使用了前面领用两张(如下图所示第3天)
(2)那么是先求出3月10号这一天一共改动了哪个时间的数据,包含新增和更新的数据,然后在dwd_fact_coupon_use取被更新的分区数据
(3)接着old的数据和3月10日最新的new增量数据(ods_coupon_use增量存储)做full outer join操作,得出完整的3-10日的全量数据,并且这部分数据在后面的insert overwrite过程中是用动态分区的方式插入的,结果就会把前面3月8日和3月9日涉及到更新的分区数据也一同更新。
(4)合并过程中有if(new.id is null,old.id,new.id)判断能优先取到new的数据,没有才取old的数据。
结果:不光插入了最新3月10日的数据,同时也更新了前面分区的数据,形成一个根据实际业务变化而整体变化的表。
完整代码:
注意:dt 是按照优惠卷领用时间 get_time 做为分区。
hive (gmall)> drop table if exists dwd_fact_coupon_use;
create external table dwd_fact_coupon_use
( `id` string COMMENT '编号',
`coupon_id` string COMMENT '优惠券 ID',
`user_id` string COMMENT 'userid',
`order_id` string COMMENT '订单 id',
`coupon_status` string COMMENT '优惠券状态',
`get_time` string COMMENT '领取时间',
`using_time` string COMMENT '使用时间(下单)',
`used_time` string COMMENT '使用时间(支付)' )
COMMENT '优惠券领用事实表'
PARTITIONED BY (`dt` string)
row format delimited fields terminated by '\t'
location '/warehouse/gmall/dwd/dwd_fact_coupon_use/';
hive (gmall)>
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_fact_coupon_use partition(dt)
select
if(new.id is null,old.id,new.id),
if(new.coupon_id is null,old.coupon_id,new.coupon_id),
if(new.user_id is null,old.user_id,new.user_id),
if(new.order_id is null,old.order_id,new.order_id),
if(new.coupon_status is null,old.coupon_status,new.coupon_status),
if(new.get_time is null,old.get_time,new.get_time),
if(new.using_time is null,old.using_time,new.using_time),
if(new.used_time is null,old.used_time,new.used_time),
date_format(if(new.get_time is null,old.get_time,new.get_time),'yyyy-MM-dd')
from
(
select
id,
coupon_id,
user_id,
order_id,
coupon_status,
get_time,
using_time,
used_time
from dwd_fact_coupon_use
where dt in --这里针对被修改的分区数据
(
select
date_format(get_time,'yyyy-MM-dd')
from ods_coupon_use
where dt='2020-03-10'
)
)old
full outer join
(
select
id,
coupon_id,
user_id,
order_id,
coupon_status,
get_time,
using_time,
used_time
from ods_coupon_use
where dt='2020-03-10'
)new
on old.id=new.id;
订单事实表(累积型快照事实表2-行转列,要学会)
这张表也是累积型事实表,主要是有以下过程–订单生命周期:创建时间=》支付时间=》取消时间=》完成时间=》退款时间=》退款完成时间,同样需记录某个订单的业务流程变化数据。
但这里有个难点是,它的来源表订单信息表记录的某一订单的操作过程都是分开不同行的,现在需要合并在同一行,也就是行转列问题,这里采用的是将所有行转换成一个一个键值对存放在一个数组,再用str_to_map 函数取出。
具体行转列的代码如第二段所示,其中collect_set要和group by(order_id)配合使用,把相同组的id拼在一起。
hive (gmall)>
drop table if exists dwd_fact_order_info;
create external table dwd_fact_order_info (
`id` string COMMENT '订单编号',
`order_status` string COMMENT '订单状态',
`user_id` string COMMENT '用户 id',
`out_trade_no` string COMMENT '支付流水号',
`create_time` string COMMENT '创建时间(未支付状态)',
`payment_time` string COMMENT '支付时间(已支付状态)',
`cancel_time` string COMMENT '取消时间(已取消状态)',
`finish_time` string COMMENT '完成时间(已完成状态)',
`refund_time` string COMMENT '退款时间(退款中状态)',
`refund_finish_time` string COMMENT '退款完成时间(退款完成状态)',
`province_id` string COMMENT '省份 ID',
`activity_id` string COMMENT '活动 ID',
`original_total_amount` string COMMENT '原价金额',
`benefit_reduce_amount` string COMMENT '优惠金额',
`feight_fee` string COMMENT '运费',
`final_total_amount` decimal(10,2) COMMENT '订单金额'
)
PARTITIONED BY (`dt` string)
stored as parquet
location '/warehouse/gmall/dwd/dwd_fact_order_info/'
tblproperties ("parquet.compression"="lzo");
#开启非严格模式
hive (gmall)> set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_fact_order_info partition(dt)
select
if(new.id is null,old.id,new.id),
if(new.order_status is null,old.order_status,new.order_status),
if(new.user_id is null,old.user_id,new.user_id),
if(new.out_trade_no is null,old.out_trade_no,new.out_trade_no),
if(new.tms['1001'] is null,old.create_time,new.tms['1001']),--1001 对应未支付状态
if(new.tms['1002'] is null,old.payment_time,new.tms['1002']),
if(new.tms['1003'] is null,old.cancel_time,new.tms['1003']),
if(new.tms['1004'] is null,old.finish_time,new.tms['1004']),
if(new.tms['1005'] is null,old.refund_time,new.tms['1005']),
if(new.tms['1006'] is null,old.refund_finish_time,new.tms['1006']),
if(new.province_id is null,old.province_id,new.province_id),
if(new.activity_id is null,old.activity_id,new.activity_id),
if(new.original_total_amount is null,old.original_total_amount,new.original_total_amount),
if(new.benefit_reduce_amount is null,old.benefit_reduce_amount,new.benefit_reduce_amount),
if(new.feight_fee is null,old.feight_fee,new.feight_fee),
if(new.final_total_amount is null,old.final_total_amount,new.final_total_amount),
date_format(if(new.tms['1001'] is null,old.create_time,new.tms['1001']),'yyyy-MM-dd')
from
(
select
id,
order_status,
user_id,
out_trade_no,
create_time,
payment_time,
cancel_time,
finish_time,
refund_time,
refund_finish_time,
province_id,
activity_id,
original_total_amount,
benefit_reduce_amount,
feight_fee,
final_total_amount
from dwd_fact_order_info
where dt in
(
select
date_format(create_time,'yyyy-MM-dd')
from ods_order_info
where dt='2020-03-10'
)
)old
full outer join
(
select
info.id,
info.order_status,
info.user_id,
info.out_trade_no,
info.province_id,
act.activity_id,
log.tms,
info.original_total_amount,
info.benefit_reduce_amount,
info.feight_fee,
info.final_total_amount
from
(
select
order_id,
#这里是主要的行转列代码
#先是concat(order_status,'=',operate_time) =》
#1001=2020-03-10 00:00:00.0
#1002=2020-03-10 00:00:00.0
#1005=2020-03-10 00:00:00.0
#然后collect_set(concat(order_status,'=',operate_time)) =》
#["1001=2020-03-10 00:00:00.0","1002=2020-03-10 00:00:00.0","1005=2020-03-10 00:00:00.0"]
#注意collect_set + group by 这个组合。
#再往后是concat_ws(',', collect_set(concat(order_status,'=',operate_time))) =》
#1001=2020-03-10 00:00:00.0,1002=2020-03-10 00:00:00.0,1005=2020-03-10 00:00:00.0
#形成对数组内的每一项都用,号拼接
#最后str_to_map(concat_ws(',',collect_set(concat(order_status,'=',operate_time))), ',' , '=') =》 {"1001":"2020-03-10 00:00:00.0","1002":"2020-03-10 00:00:00.0","1005":"2020-03-10 00:00:00.0"}转换成键值对。
#
str_to_map(concat_ws(',',collect_set(concat(order_status,'=',operate_time))),',','=')
tms
from ods_order_status_log
where dt='2020-03-10'
group by order_id
)log
join
(
select * from ods_order_info where dt='2020-03-10'
)info
on log.order_id=info.id
left join
(
select * from ods_activity_order where dt='2020-03-10'
)act
on log.order_id=act.order_id
)new
on old.id=new.id;
用户表中的数据每日既有可能新增,也有可能修改,但修改频率并不高,属于缓慢变化维度,此处采用拉链表存储用户维度数据。(面试可能会问怎么做拉链表或者有这样的场景要怎么解决)
(1)拉链表,记录每条信息的生命周期,一旦一条记录的生命周期结束,就重新开始一条新的记录,并把当前日期放入生效开始日期。
如果当前信息至今有效,在生效结束日期中会显示一个极大值(9999-99-99),类似于核心征管的登记信息表。
(2)为什么要做拉链表?
拉链表适合于:数据会发生变化,但是大部分是不变的(也就是缓慢变化维)
比如用户信息会发生变化,但是变化频率不高,如果数据量有一定规模,按照每日全量的方式保存效率比较低,如1亿用户 * 365天,每天一份全量信息,做每日全量效率就很低。
(3)如何使用拉链表?
通过,开始时间 <= 某个日期 ,并且结束时间 >= 某个日期,能够得到某个时间点的数据全量切片。
比如:
(4)拉链表形成过程和制作流程图
这里要注意第五步中id = 2 的李四,他的结束时间是需要更新的。
(5)拉链表制作过程
通常来说,拉链表一定要有类似的开始时间和结束时间,没有就需要创造。比如这里建表的有效开始日期和有效结束日期。这两个字段是原来ods层的表没有的。
#建表
hive (gmall)> drop table if exists dwd_dim_user_info_his;
create external table dwd_dim_user_info_his(
`id` string COMMENT '用户 id',
`name` string COMMENT '姓名',
`birthday` string COMMENT '生日',
`gender` string COMMENT '性别',
`email` string COMMENT '邮箱',
`user_level` string COMMENT '用户等级',
`create_time` string COMMENT '创建时间',
`operate_time` string COMMENT '操作时间',
`start_date` string COMMENT '有效开始日期',
`end_date` string COMMENT '有效结束日期'
) COMMENT '订单拉链表'
stored as parquet
location '/warehouse/gmall/dwd/dwd_dim_user_info_his/'
tblproperties ("parquet.compression"="lzo");
#第一步:初始化拉链表,加入开始时间和结束时间
hive (gmall)> insert overwrite table dwd_dim_user_info_his
select
id,
name,
birthday,
gender,
email,
user_level,
create_time,
operate_time,
'2020-03-10',
'9999-99-99'
from ods_user_info oi
where oi.dt='2020-03-10';
如何获得每日变动表?
a.最好表内有创建时间和变动时间
b.如果没有,可以利用第三方工具监控比如canal,监控MySQL的实时变化进行记录(比较麻烦)
c.逐行对比前后两天的数据,检查md5(concat(所有可能变化的字段))是否相同(low),这种不推荐
d.要求业务数据库提供变动流水(也不推荐)
这里因为ods_order_info本身导入的就是新增变动的数据,所以只需要跑一遍需要的分区数据就可以了
#sqoop跑2020-03-11数据
mysqlTohdfs.sh all 2020-03-11
#ods层数据导入
hdfs_to_ods_db.sh all 2020-03-11
#第二步:先合并变动信息,再追加新增信息,插入到临时表中
#建临时表
hive (gmall)> drop table if exists dwd_dim_user_info_his_tmp;
create external table dwd_dim_user_info_his_tmp(
`id` string COMMENT '用户 id',
`name` string COMMENT '姓名',
`birthday` string COMMENT '生日',
`gender` string COMMENT '性别',
`email` string COMMENT '邮箱',
`user_level` string COMMENT '用户等级',
`create_time` string COMMENT '创建时间',
`operate_time` string COMMENT '操作时间',
`start_date` string COMMENT '有效开始日期',
`end_date` string COMMENT '有效结束日期'
) COMMENT '订单拉链临时表'
stored as parquet
location '/warehouse/gmall/dwd/dwd_dim_user_info_his_tmp/'
tblproperties ("parquet.compression"="lzo");
#注意,前面也提到ods_user_info存放的就是每日新增和变化的数据
#而union all下面那部分,因为有left join和if,其实是对变化的id更新它的结束日期。
#然后这两部分合并就得到一个历史的全量数据。
#这个脚本在第一天初始化时应该也适用的,比如union all上面部分就是初始化第一天要存的数据,下面部分它第一天本身是left join不到数据的。
hive (gmall)> insert overwrite table dwd_dim_user_info_his_tmp
select * from
(
select
id,
name,
birthday,
gender,
email,
user_level,
create_time,
operate_time,
'2020-03-11' start_date,
'9999-99-99' end_date
from ods_user_info where dt='2020-03-11'
union all
select
uh.id,
uh.name,
uh.birthday,
uh.gender,
uh.email,
uh.user_level,
uh.create_time,
uh.operate_time,
uh.start_date,
if(ui.id is not null and uh.end_date='9999-99-99', date_add(ui.dt,-1),
uh.end_date) end_date
from dwd_dim_user_info_his uh
left join
(
select
*
from ods_user_info
where dt='2020-03-11'
) ui on uh.id=ui.id
)his
order by his.id, start_date;
#第三步:把临时表覆盖给拉链表,全部分区。
hive (gmall)> insert overwrite table dwd_dim_user_info_his
select * from dwd_dim_user_info_his_tmp;
(6)拉链表总结过程
这一层的统计主题,包括下一层DWT的主题,主要还是看ADS层需要什么数据才去建立的。
活跃用户:打开应用的用户即为活跃用户,不考虑用户的使用情况。每天一台设备打开多次会被计
为一个活跃用户。
新增用户:卸载再安装的设备,不会被算作一次新增。新增用户包括日新增用户、周新增用户、月
新增用户。
周(月)活跃用户:某个自然周(月)内启动过应用的用户,该周(月)内的多次启动只记一个活跃用户。
月活跃率:月活跃用户与截止到该月累计的用户总和之间的比例。
沉默用户:用户仅在安装当天(次日)启动一次,后续时间无再启动行为。该指标可以反映新增用
户质量和用户与 APP 的匹配程度
版本分布:不同版本的周内各天新增用户数,活跃用户数和启动次数。利于判断 APP 各个版本之
间的优劣和用户行为习惯。
本周回流用户:上周未启动过应用,本周启动了应用的用户。
忠诚用户:连续活跃 5 周以上的用户
近期流失用户:连续 n(2<= n <= 4)周没有启动应用的用户。(第 n+1 周没有启动过)
留存用户:某段时间内的新增用户,经过一段时间后,仍然使用应用的被认作是留存用户;这部分
用户占当时新增用户的比例即是留存率。
用户新鲜度:每天启动应用的新老用户比例,即新增用户数占活跃用户数的比例。
启动计算次数标准:IOS 平台应用退到后台就算一次独立的启动;Android 平台我们规定,两次启动之间的
间隔小于 30 秒,被计算一次启动。用户在使用过程中,若因收发短信或接电话等退出应用
30 秒又再次返回应用中,那这两次行为应该是延续而非独立的,所以可以被算作一次使用
行为,即一次启动。业内大多使用 30 秒这个标准,但用户还是可以自定义此时间间隔
统计各个主题对象的当天行为
每日设备行为,主要按照设备 id 统计。
daycount指每日(粒度)
然后这种经常被使用的宽表,很少被压缩,就如下面的建表语句没有再指定压缩。
concat_ws做了一步分割,方便后续步骤继续对这张表加工截取。
#查询结果:
hive (gmall)> select * from dws_uv_detail_daycount where dt='2020-03-10';
DWS 层的宽表字段,是站在不同维度的视角去看事实表。重点关注事实表的度量值。
每日会员行为,主要统计该用户一下度量值,但这些值都存放在多张不同的DWD表,这里可以用with as + insert overwrite结构来写方便些(比放到insert下面一个个join清晰些)。
#建表
hive (gmall)> drop table if exists dws_user_action_daycount;
create external table dws_user_action_daycount
(
user_id string comment '用户 id',
login_count bigint comment '登录次数',
cart_count bigint comment '加入购物车次数',
cart_amount double comment '加入购物车金额',
order_count bigint comment '下单次数',
order_amount
decimal(16,2) comment '下单金额',
payment_count bigint
comment '支付次数',
payment_amount decimal(16,2) comment '支付金额'
) COMMENT '每日用户行为'
PARTITIONED BY (`dt` string)
stored as parquet
location '/warehouse/gmall/dws/dws_user_action_daycount/'
tblproperties ("parquet.compression"="lzo");
#企业的标准写法,这种用with as + union all 比用left join直观清晰些,还不用join on条件。但具体还是看每个公司的习惯吧。
#没有值的就用0代替,后面sum + group by统计分析。
hive (gmall)>
with
tmp_login as
(
select
user_id,
count(*) login_count
from dwd_start_log
where dt='2020-03-10'
and user_id is not null
group by user_id
),
tmp_cart as
(
select
user_id,
count(*) cart_count,
sum(cart_price*sku_num) cart_amount
from dwd_fact_cart_info
where dt='2020-03-10'
and user_id is not null
and date_format(create_time,'yyyy-MM-dd')='2020-03-10'
group by user_id
),
tmp_order as
(
select
user_id,
count(*) order_count,
sum(final_total_amount) order_amount
from dwd_fact_order_info
where dt='2020-03-10'
group by user_id
) ,
tmp_payment as
(
select
user_id,
count(*) payment_count,
sum(payment_amount) payment_amount
from dwd_fact_payment_info
where dt='2020-03-10'
group by user_id
)
insert overwrite table dws_user_action_daycount partition(dt='2020-03-10')
select
user_actions.user_id,
sum(user_actions.login_count),
sum(user_actions.cart_count),
sum(user_actions.cart_amount),
sum(user_actions.order_count),
sum(user_actions.order_amount),
sum(user_actions.payment_count),
sum(user_actions.payment_amount)
from
(
select
user_id,
login_count,
0 cart_count,
0 cart_amount,
0 order_count,
0 order_amount,
0 payment_count,
0 payment_amount
from
tmp_login
union all
select
user_id,
0 login_count,
cart_count,
cart_amount,
0 order_count,
0 order_amount,
0 payment_count,
0 payment_amount
from
tmp_cart
union all
select
user_id,
0 login_count,
0 cart_count,
0 cart_amount,
order_count,
order_amount,
0 payment_count,
0 payment_amount
from tmp_order
union all
select
user_id,
0 login_count,
0 cart_count,
0 cart_amount,
0 order_count,
0 order_amount,
payment_count,
payment_amount
from tmp_payment
) user_actions
group by user_id;
hive (gmall)>
drop table if exists dws_sku_action_daycount;
create external table dws_sku_action_daycount
(
sku_id string comment 'sku_id',
order_count bigint comment '被下单次数',
order_num bigint comment '被下单件数',
order_amount decimal(16,2) comment '被下单金额',
payment_count bigint comment '被支付次数',
payment_num bigint comment '被支付件数',
payment_amount decimal(16,2) comment '被支付金额',
refund_count bigint comment '被退款次数',
refund_num bigint comment '被退款件数',
refund_amount decimal(16,2) comment '被退款金额',
cart_count bigint comment '被加入购物车次数',
cart_num bigint comment '被加入购物车件数',
favor_count bigint comment '被收藏次数',
appraise_good_count bigint comment '好评数',
appraise_mid_count bigint comment '中评数',
appraise_bad_count bigint comment '差评数',
appraise_default_count bigint comment '默认评价数'
) COMMENT '每日商品行为'
PARTITIONED BY (`dt` string)
stored as parquet
location '/warehouse/gmall/dws/dws_sku_action_daycount/'
tblproperties ("parquet.compression"="lzo");
hive (gmall)>
with
tmp_order as
(
select
sku_id,
count(*) order_count,
sum(sku_num) order_num,
sum(total_amount) order_amount
from dwd_fact_order_detail
where dt='2020-03-10'
group by sku_id
),
tmp_payment as
(
select
sku_id,
count(*) payment_count,
sum(sku_num) payment_num,
sum(total_amount) payment_amount
from dwd_fact_order_detail
where dt='2020-03-10'
and order_id in
(
select
id
from dwd_fact_order_info
where (dt='2020-03-10' or dt=date_add('2020-03-10',-1))
and date_format(payment_time,'yyyy-MM-dd')='2020-03-10')
group by sku_id
),
tmp_refund as
(
select
sku_id,
count(*) refund_count,
sum(refund_num) refund_num,
sum(refund_amount) refund_amount
from dwd_fact_order_refund_info
where dt='2020-03-10'
group by sku_id
),
tmp_cart as
(
select
sku_id,
count(*) cart_count,
sum(sku_num) cart_num
from dwd_fact_cart_info
where dt='2020-03-10'
and date_format(create_time,'yyyy-MM-dd')='2020-03-10'
group by sku_id
),
tmp_favor as
(
select
sku_id,
count(*) favor_count
from dwd_fact_favor_info
where dt='2020-03-10'
and date_format(create_time,'yyyy-MM-dd')='2020-03-10'
group by sku_id
),
tmp_appraise as
(
select
sku_id,
sum(if(appraise='1201',1,0)) appraise_good_count,
sum(if(appraise='1202',1,0)) appraise_mid_count,
sum(if(appraise='1203',1,0)) appraise_bad_count,
sum(if(appraise='1204',1,0)) appraise_default_count
from dwd_fact_comment_info
where dt='2020-03-10'
group by sku_id
)
insert overwrite table dws_sku_action_daycount partition(dt='2020-03-10')
select
sku_id,
sum(order_count),
sum(order_num),
sum(order_amount),
sum(payment_count),
sum(payment_num),
sum(payment_amount),
sum(refund_count),
sum(refund_num),
sum(refund_amount),
sum(cart_count),
sum(cart_num),
sum(favor_count),
sum(appraise_good_count),
sum(appraise_mid_count),
sum(appraise_bad_count),
sum(appraise_default_count)
from
(
select
sku_id,
order_count,
order_num,
order_amount,
0 payment_count,
0 payment_num,
0 payment_amount,
0 refund_count,
0 refund_num,
0 refund_amount,
0 cart_count,
0 cart_num,
0 favor_count,
0 appraise_good_count,
0 appraise_mid_count,
0 appraise_bad_count,
0 appraise_default_count
rom tmp_order
union all
select
sku_id,
0 order_count,
0 order_num,
0 order_amount,
payment_count,
payment_num,
payment_amount,
0 refund_count,
0 refund_num,
0 refund_amount,
0 cart_count,
0 cart_num,
0 favor_count,
0 appraise_good_count,
0 appraise_mid_count,
0 appraise_bad_count,
0 appraise_default_count
from tmp_payment
union all
select
sku_id,
0 order_count,
0 order_num,
0 order_amount,
0 payment_count,
0 payment_num,
0 payment_amount,
refund_count,
refund_num,
refund_amount,
0 cart_count,
0 cart_num,
0 favor_count,
0 appraise_good_count,
0 appraise_mid_count,
0 appraise_bad_count,
0 appraise_default_count
from tmp_refund
union all
select
sku_id,
0 order_count,
0 order_num,
0 order_amount,
0 payment_count,
0 payment_num,
0 payment_amount,
0 refund_count,
0 refund_num,
0 refund_amount,
cart_count,
cart_num,
0 favor_count,
0 appraise_good_count,
0 appraise_mid_count,
0 appraise_bad_count,
0 appraise_default_count
from tmp_cart
union all
select
sku_id,
0 order_count,
0 order_num,
0 order_amount,
0 payment_count,
0 payment_num,
0 payment_amount,
0 refund_count,
0 refund_num,
0 refund_amount,
0 cart_count,
0 cart_num,
favor_count,
0 appraise_good_count,
0 appraise_mid_count,
0 appraise_bad_count,
0 appraise_default_count
from tmp_favor
union all
select
sku_id,
0 order_count,
0 order_num,
0 order_amount,
0 payment_count,
0 payment_num,
0 payment_amount,
0 refund_count,
0 refund_num,
0 refund_amount,
0 cart_count,
0 cart_num,
0 favor_count,
appraise_good_count,
appraise_mid_count,
appraise_bad_count,
appraise_default_count
from tmp_appraise
)tmp
group by sku_id;
跟优惠券的表逻辑一样。
注意where (dt=‘2020-03-10’ or dt=date_add(‘2020-03-10’,-1))存在03-09晚上下单但却在03-10支付的情况。
这里以每日社保和每日会员行为为例,其他需要的就加到sql中。
注意检查日期和schema的值。
[atguigu@hadoop102 bin]$ vim dwd_to_dws.sh
#!/bin/bash
APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
insert overwrite table ${APP}.dws_uv_detail_daycount partition(dt='$do_date')
select
mid_id,
concat_ws('|', collect_set(user_id)) user_id,
concat_ws('|', collect_set(version_code)) version_code,
concat_ws('|', collect_set(version_name)) version_name,
concat_ws('|', collect_set(lang))lang,
concat_ws('|', collect_set(source)) source,
concat_ws('|', collect_set(os)) os,
concat_ws('|', collect_set(area)) area,
concat_ws('|', collect_set(model)) model,
concat_ws('|', collect_set(brand)) brand,
concat_ws('|', collect_set(sdk_version)) sdk_version,
concat_ws('|', collect_set(gmail)) gmail,
concat_ws('|', collect_set(height_width)) height_width,
concat_ws('|', collect_set(app_time)) app_time,
concat_ws('|', collect_set(network)) network,
concat_ws('|', collect_set(lng)) lng,
concat_ws('|', collect_set(lat)) lat,
count(*) login_count
from ${APP}.dwd_start_log
where dt='$do_date'
group by mid_id;
with
tmp_login as
(
select
user_id,
count(*) login_count
from ${APP}.dwd_start_log
where dt='$do_date'
and user_id is not null
group by user_id
),
tmp_cart as
(
select
user_id,
count(*) cart_count,
sum(cart_price*sku_num) cart_amount
from ${APP}.dwd_fact_cart_info
where dt='$do_date'
and user_id is not null
and date_format(create_time,'yyyy-MM-dd')='$do_date'
group by user_id
),
tmp_order as
(
select
user_id,
count(*) order_count,
sum(final_total_amount) order_amount
from ${APP}.dwd_fact_order_info
where dt='$do_date'
group by user_id
) ,
tmp_payment as
(
select
user_id,
count(*) payment_count,
sum(payment_amount) payment_amount
from ${APP}.dwd_fact_payment_info
where dt='$do_date'
group by user_id
)
insert overwrite table ${APP}.dws_user_action_daycount partition(dt='$do_date')
select
user_actions.user_id,
sum(user_actions.login_count),
sum(user_actions.cart_count),
sum(user_actions.cart_amount),
sum(user_actions.order_count),
sum(user_actions.order_amount),
sum(user_actions.payment_count),
sum(user_actions.payment_amount)
from
(
select
user_id,
login_count,
0 cart_count,
0 cart_amount,
0 order_count,
0 order_amount,
0 payment_count,
0 payment_amount
from
tmp_login
union all
select
user_id,
0 login_count,
cart_count,
cart_amount,
0 order_count,
0 order_amount,
0 payment_count,
0 payment_amount
from
tmp_cart
union all
select
user_id,
0 login_count,
0 cart_count,
0 cart_amount,
order_count,
order_amount,
0 payment_count,
0 payment_amount
from tmp_order
union all
select
user_id,
0 login_count,
0 cart_count,
0 cart_amount,
0 order_count,
0 order_amount,
payment_count,
payment_amount
from tmp_payment
) user_actions
group by user_id;
"
$hive -e "$sql"
问:宽表是怎么建的,答:从维度的角度看问题,以及考虑需要哪些度量值。
注意,这里的宽表没必要再建成分区表,因为存的是所有历史的一个统计结果。
逻辑也是将old数据和new数据结合,full outer join。
注意下面红色字体的逻辑,还有累计活跃天数,注意是天数而不是次数。
如下的建表语句,难点有:
(如何一起统计最近30天和最新时间的累计值?)
hive (gmall)>
drop table if exists dwt_user_topic;
create external table dwt_user_topic
(
user_id string comment '用户 id',
login_date_first string comment '首次登录时间',
login_date_last string comment '末次登录时间',
login_count bigint comment '累积登录天数',
login_last_30d_count bigint comment '最近 30 日登录天数',
order_date_first string comment '首次下单时间',
order_date_last string comment '末次下单时间',
order_count bigint comment '累积下单次数',
order_amount decimal(16,2) comment '累积下单金额',
order_last_30d_count bigint comment '最近 30 日下单次数',
order_last_30d_amount bigint comment '最近 30 日下单金额',
payment_date_first string comment '首次支付时间',
payment_date_last string comment '末次支付时间',
payment_count decimal(16,2) comment '累积支付次数',
payment_amount decimal(16,2) comment '累积支付金额',
payment_last_30d_count decimal(16,2) comment '最近 30 日支付次数',
payment_last_30d_amount decimal(16,2) comment '最近 30 日支付金额'
)COMMENT '用户主题宽表'
stored as parquet
location '/warehouse/gmall/dwt/dwt_user_topic/'
tblproperties ("parquet.compression"="lzo");
#累积型事实表,用old 和 new的数据相互full outer join得出来。
#dws_user_action_daycount表是在DWS层事先加工好的每日会员行为表
hive (gmall)>
insert overwrite table dwt_user_topic
select
nvl(new.user_id,old.user_id),
if(old.login_date_first is null and new.login_count>0,'2020-03-10',old.login_date_first),
if(new.login_count>0,'2020-03-10',old.login_date_last),
nvl(old.login_count,0)+if(new.login_count>0,1,0),
nvl(new.login_last_30d_count,0),
if(old.order_date_first is null and new.order_count>0,'2020-03-10',old.order_date_first),
if(new.order_count>0,'2020-03-10',old.order_date_last),
nvl(old.order_count,0)+nvl(new.order_count,0),
nvl(old.order_amount,0)+nvl(new.order_amount,0),
nvl(new.order_last_30d_count,0),
nvl(new.order_last_30d_amount,0),
if(old.payment_date_first is null new.payment_count>0,'2020-03-10',old.payment_date_first),
if(new.payment_count>0,'2020-03-10',old.payment_date_last),
nvl(old.payment_count,0)+nvl(new.payment_count,0),
nvl(old.payment_amount,0)+nvl(new.payment_amount,0),
nvl(new.payment_last_30d_count,0),
nvl(new.payment_last_30d_amount,0)
from
dwt_user_topic old
full outer join
(
#这里解决最近30天和同时要统计最新日期的数据,解决方式很巧妙,它在dt>=date_add( '2020-03-10',-30) 把最近30天的数据一次性取出来,然后又在sum(if(dt='2020-03-10',login_count,0)) login_count,判定取出最新日期的一个累计数据。值得学习一下。
select
user_id,
sum(if(dt='2020-03-10',login_count,0)) login_count,
sum(if(dt='2020-03-10',order_count,0)) order_count,
sum(if(dt='2020-03-10',order_amount,0)) order_amount,
sum(if(dt='2020-03-10',payment_count,0)) payment_count,
sum(if(dt='2020-03-10',payment_amount,0)) payment_amount,
sum(if(login_count>0,1,0)) login_last_30d_count, --最近 30 日登录天数,有登录就等于1,然后sum合计
sum(order_count) order_last_30d_count,
sum(order_amount) order_last_30d_amount,
sum(payment_count) payment_last_30d_count,
sum(payment_amount) payment_last_30d_amount
from dws_user_action_daycount
where dt>=date_add( '2020-03-10',-30)
group by user_id
)new
on old.user_id=new.user_id;
写成固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert overwrite table ${APP}.dwt_user_topic
select
nvl(new.user_id,old.user_id),
if(old.login_date_first is null and new.login_count>0,'$do_date',old.login_date_first),
if(new.login_count>0,'$do_date',old.login_date_last),
nvl(old.login_count,0)+if(new.login_count>0,1,0),
nvl(new.login_last_30d_count,0),
if(old.order_date_first is null and new.order_count>0,'$do_date',old.order_date_first),
if(new.order_count>0,'$do_date',old.order_date_last),
nvl(old.order_count,0)+nvl(new.order_count,0),
nvl(old.order_amount,0)+nvl(new.order_amount,0),
nvl(new.order_last_30d_count,0),
nvl(new.order_last_30d_amount,0),
if(old.payment_date_first is and new.payment_count>0,'$do_date',old.payment_date_first),
if(new.payment_count>0,'$do_date',old.payment_date_last),
nvl(old.payment_count,0)+nvl(new.payment_count,0),
nvl(old.payment_amount,0)+nvl(new.payment_amount,0),
nvl(new.payment_last_30d_count,0),
nvl(new.payment_last_30d_amount,0)
from
(
select
*
from ${APP}.dwt_user_topic
)old
full outer join
(
select
user_id,
sum(if(dt='$do_date',login_count,0)) login_count,
sum(if(dt='$do_date',order_count,0)) order_count,
sum(if(dt='$do_date',order_amount,0)) order_amount,
sum(if(dt='$do_date',payment_count,0)) payment_count,
sum(if(dt='$do_date',payment_amount,0)) payment_amount,
sum(if(order_count>0,1,0)) login_last_30d_count,
sum(order_count) order_last_30d_count,
sum(order_amount) order_last_30d_amount,
sum(payment_count) payment_last_30d_count,
sum(payment_amount) payment_last_30d_amount
from ${APP}.dws_user_action_daycount
where dt>=date_add( '$do_date',-30)
group by user_id
)new
on old.user_id=new.user_id;
"
$hive -e "$sql"
思路和会员主题宽表一样。
hive (gmall)>
drop table if exists dwt_sku_topic;
create external table dwt_sku_topic
(
sku_id string comment 'sku_id',
spu_id string comment 'spu_id',
order_last_30d_count bigint comment '最近 30 日被下单次数',
order_last_30d_num bigint comment '最近 30 日被下单件数',
order_last_30d_amount decimal(16,2) comment '最近 30 日被下单金额',
order_count bigint comment '累积被下单次数',
order_num bigint comment '累积被下单件数',
order_amount decimal(16,2) comment '累积被下单金额',
payment_last_30d_count bigint comment '最近 30 日被支付次数',
payment_last_30d_num bigint comment '最近 30 日被支付件数',
payment_last_30d_amount decimal(16,2) comment '最近 30 日被支付金额',
payment_count bigint comment '累积被支付次数',
payment_num bigint comment '累积被支付件数',
payment_amount decimal(16,2) comment '累积被支付金额',
refund_last_30d_count bigint comment '最近三十日退款次数',
refund_last_30d_num bigint comment '最近三十日退款件数',
refund_last_30d_amount decimal(10,2) comment '最近三十日退款金额',
refund_count bigint comment '累积退款次数',
refund_num bigint comment '累积退款件数',
refund_amount decimal(10,2) comment '累积退款金额',
cart_last_30d_count bigint comment '最近 30 日被加入购物车次数',
cart_last_30d_num bigint comment '最近 30 日被加入购物车件数',
cart_count bigint comment '累积被加入购物车次数',
cart_num bigint comment '累积被加入购物车件数',
favor_last_30d_count bigint comment '最近 30 日被收藏次数',
favor_count bigint comment '累积被收藏次数',
appraise_last_30d_good_count bigint comment '最近 30 日好评数',
appraise_last_30d_mid_count bigint comment '最近 30 日中评数',
appraise_last_30d_bad_count bigint comment '最近 30 日差评数',
appraise_last_30d_default_count bigint comment '最近 30 日默认评价数',
appraise_good_count bigint comment '累积好评数',
appraise_mid_count bigint comment '累积中评数',
appraise_bad_count bigint comment '累积差评数',
appraise_default_count bigint comment '累积默认评价数'
)COMMENT '商品主题宽表'
stored as parquet
location '/warehouse/gmall/dwt/dwt_sku_topic/'
tblproperties ("parquet.compression"="lzo");
hive (gmall)>
insert overwrite table dwt_sku_topic
select
nvl(new.sku_id,old.sku_id),
sku_info.spu_id,
nvl(new.order_count30,0),
nvl(new.order_num30,0),
nvl(new.order_amount30,0),
nvl(old.order_count,0) + nvl(new.order_count,0),
nvl(old.order_num,0) + nvl(new.order_num,0),
nvl(old.order_amount,0) + nvl(new.order_amount,0),
nvl(new.payment_count30,0),
nvl(new.payment_num30,0),
nvl(new.payment_amount30,0),
nvl(old.payment_count,0) + nvl(new.payment_count,0),
nvl(old.payment_num,0) + nvl(new.payment_count,0),
nvl(old.payment_amount,0) + nvl(new.payment_count,0),
nvl(new.refund_count30,0),
nvl(new.refund_num30,0),
nvl(new.refund_amount30,0),
nvl(old.refund_count,0) + nvl(new.refund_count,0),
nvl(old.refund_num,0) + nvl(new.refund_num,0),
nvl(old.refund_amount,0) + nvl(new.refund_amount,0),
nvl(new.cart_count30,0),
nvl(new.cart_num30,0),
nvl(old.cart_count,0) + nvl(new.cart_count,0),
nvl(old.cart_num,0) + nvl(new.cart_num,0),
nvl(new.favor_count30,0),
nvl(old.favor_count,0) + nvl(new.favor_count,0),
nvl(new.appraise_good_count30,0),
nvl(new.appraise_mid_count30,0),
nvl(new.appraise_bad_count30,0),
nvl(new.appraise_default_count30,0) ,
nvl(old.appraise_good_count,0) + nvl(new.appraise_good_count,0),
nvl(old.appraise_mid_count,0) + nvl(new.appraise_mid_count,0),
nvl(old.appraise_bad_count,0) + nvl(new.appraise_bad_count,0),
nvl(old.appraise_default_count,0) + nvl(new.appraise_default_count,0)
from
(
select
sku_id,
spu_id,
order_last_30d_count,
order_last_30d_num,
order_last_30d_amount,
order_count,
order_num,
order_amount ,
payment_last_30d_count,
payment_last_30d_num,
payment_last_30d_amount,
payment_count,
payment_num,
payment_amount,
refund_last_30d_count,
refund_last_30d_num,
refund_last_30d_amount,
refund_count,
refund_num,
refund_amount,
cart_last_30d_count,
cart_last_30d_num,
cart_count,
cart_num,
favor_last_30d_count,
favor_count,
appraise_last_30d_good_count,
appraise_last_30d_mid_count,
appraise_last_30d_bad_count,
appraise_last_30d_default_count,
appraise_good_count,
appraise_mid_count,
appraise_bad_count,
appraise_default_count
from dwt_sku_topic
)old
full outer join
(
select
sku_id,
sum(if(dt='2020-03-10', order_count,0 )) order_count,
sum(if(dt='2020-03-10',order_num ,0 )) order_num,
sum(if(dt='2020-03-10',order_amount,0 )) order_amount ,
sum(if(dt='2020-03-10',payment_count,0 )) payment_count,
sum(if(dt='2020-03-10',payment_num,0 )) payment_num,
sum(if(dt='2020-03-10',payment_amount,0 )) payment_amount,
sum(if(dt='2020-03-10',refund_count,0 )) refund_count,
sum(if(dt='2020-03-10',refund_num,0 )) refund_num,
sum(if(dt='2020-03-10',refund_amount,0 )) refund_amount,
sum(if(dt='2020-03-10',cart_count,0 )) cart_count,
sum(if(dt='2020-03-10',cart_num,0 )) cart_num,
sum(if(dt='2020-03-10',favor_count,0 )) favor_count,
sum(if(dt='2020-03-10',appraise_good_count,0 )) appraise_good_count,
sum(if(dt='2020-03-10',appraise_mid_count,0 ) ) appraise_mid_count ,
sum(if(dt='2020-03-10',appraise_bad_count,0 )) appraise_bad_count,
sum(if(dt='2020-03-10',appraise_default_count,0 )) appraise_default_count,
sum(order_count) order_count30 ,
sum(order_num) order_num30,
sum(order_amount) order_amount30,
sum(payment_count) payment_count30,
sum(payment_num) payment_num30,
sum(payment_amount) payment_amount30,
sum(refund_count) refund_count30,
sum(refund_num) refund_num30,
sum(refund_amount) refund_amount30,
sum(cart_count) cart_count30,
sum(cart_num) cart_num30,
sum(favor_count) favor_count30,
sum(appraise_good_count) appraise_good_count30,
sum(appraise_mid_count) appraise_mid_count30,
sum(appraise_bad_count) appraise_bad_count30,
sum(appraise_default_count) appraise_default_count30
from dws_sku_action_daycount
where dt >= date_add ('2020-03-10', -30)
group by sku_id
)new
on new.sku_id = old.sku_id
left join
(select * from dwd_dim_sku_info where dt='2020-03-10') sku_info
on nvl(new.sku_id,old.sku_id)= sku_info.id;
写成脚本如下,这里用with as 来概括多张join表
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
with
sku_act as
(
select
sku_id,
sum(if(dt='$do_date', order_count,0 )) order_count,
sum(if(dt='$do_date',order_num ,0 )) order_num,
sum(if(dt='$do_date',order_amount,0 )) order_amount ,
sum(if(dt='$do_date',payment_count,0 )) payment_count,
sum(if(dt='$do_date',payment_num,0 )) payment_num,
sum(if(dt='$do_date',payment_amount,0 )) payment_amount,
sum(if(dt='$do_date',refund_count,0 )) refund_count,
sum(if(dt='$do_date',refund_num,0 )) refund_num,
sum(if(dt='$do_date',refund_amount,0 )) refund_amount,
sum(if(dt='$do_date',cart_count,0 )) cart_count,
sum(if(dt='$do_date',cart_num,0 )) cart_num,
sum(if(dt='$do_date',favor_count,0 )) favor_count,
sum(if(dt='$do_date',appraise_good_count,0 )) appraise_good_count,
sum(if(dt='$do_date',appraise_mid_count,0 ) ) appraise_mid_count ,
sum(if(dt='$do_date',appraise_bad_count,0 )) appraise_bad_count,
sum(if(dt='$do_date',appraise_default_count,0 )) appraise_default_count,
sum( order_count ) order_count30 ,
sum( order_num ) order_num30,
sum(order_amount ) order_amount30,
sum(payment_count ) payment_count30,
sum(payment_num ) payment_num30,
sum(payment_amount ) payment_amount30,
sum(refund_count ) refund_count30,
sum(refund_num ) refund_num30,
sum(refund_amount ) refund_amount30,
sum(cart_count ) cart_count30,
sum(cart_num ) cart_num30,
sum(favor_count ) favor_count30,
sum(appraise_good_count ) appraise_good_count30,
sum(appraise_mid_count ) appraise_mid_count30,
sum(appraise_bad_count ) appraise_bad_count30,
sum(appraise_default_count ) appraise_default_count30
from ${APP}.dws_sku_action_daycount
where dt>=date_add ( '$do_date',-30)
group by sku_id
),
sku_topic
as
(
select
sku_id,
spu_id,
order_last_30d_count,
order_last_30d_num,
order_last_30d_amount,
order_count,
order_num,
order_amount ,
payment_last_30d_count,
payment_last_30d_num,
payment_last_30d_amount,
payment_count,
payment_num,
payment_amount,
refund_last_30d_count,
refund_last_30d_num,
refund_last_30d_amount ,
refund_count ,
refund_num ,
refund_amount ,
cart_last_30d_count ,
cart_last_30d_num ,
cart_count ,
cart_num ,
favor_last_30d_count ,
favor_count ,
appraise_last_30d_good_count ,
appraise_last_30d_mid_count ,
appraise_last_30d_bad_count ,
appraise_last_30d_default_count ,
appraise_good_count ,
appraise_mid_count ,
appraise_bad_count ,
appraise_default_count
from ${APP}.dwt_sku_topic
)
insert overwrite table ${APP}.dwt_sku_topic
select
nvl(sku_act.sku_id,sku_topic.sku_id) ,
sku_info.spu_id,
nvl (sku_act.order_count30,0) ,
nvl (sku_act.order_num30,0) ,
nvl (sku_act.order_amount30,0) ,
nvl(sku_topic.order_count,0)+ nvl (sku_act.order_count,0) ,
nvl(sku_topic.order_num,0)+ nvl (sku_act.order_num,0) ,
nvl(sku_topic.order_amount,0)+ nvl (sku_act.order_amount,0),
nvl (sku_act.payment_count30,0),
nvl (sku_act.payment_num30,0),
nvl (sku_act.payment_amount30,0),
nvl(sku_topic.payment_count,0)+ nvl (sku_act.payment_count,0) ,
nvl(sku_topic.payment_num,0)+ nvl (sku_act.payment_count,0) ,
nvl(sku_topic.payment_amount,0)+ nvl (sku_act.payment_count,0) ,
nvl (refund_count30,0),
nvl (sku_act.refund_num30,0),
nvl (sku_act.refund_amount30,0),
nvl(sku_topic.refund_count,0)+ nvl (sku_act.refund_count,0),
nvl(sku_topic.refund_num,0)+ nvl (sku_act.refund_num,0),
nvl(sku_topic.refund_amount,0)+ nvl (sku_act.refund_amount,0),
nvl(sku_act.cart_count30,0) ,
nvl(sku_act.cart_num30,0) ,
nvl(sku_topic.cart_count ,0)+ nvl (sku_act.cart_count,0),
nvl( sku_topic.cart_num ,0)+ nvl (sku_act.cart_num,0),
nvl(sku_act.favor_count30 ,0) ,
nvl (sku_topic.favor_count ,0)+ nvl (sku_act.favor_count,0),
nvl (sku_act.appraise_good_count30 ,0) ,
nvl (sku_act.appraise_mid_count30 ,0) ,
nvl (sku_act.appraise_bad_count30 ,0) ,
nvl (sku_act.appraise_default_count30 ,0) ,
nvl (sku_topic.appraise_good_count ,0)+ nvl (sku_act.appraise_good_count,0) ,
nvl (sku_topic.appraise_mid_count ,0)+ nvl (sku_act.appraise_mid_count,0) ,
nvl (sku_topic.appraise_bad_count ,0nvl (sku_act.appraise_bad_count,0) ,
nvl (sku_topic.appraise_default_count ,0)+ nvl (sku_act.appraise_default_count,0)
from sku_act
full outer join sku_topic
on sku_act.sku_id =sku_topic.sku_id
left join
(select * from ${APP}.dwd_dim_sku_info where dt='$do_date') sku_info
on nvl(sku_topic.sku_id,sku_act.sku_id)= sku_info.id;
"
$hive -e "$sql"
这里以设备主题为例,其它需要的直接加到sql中即可。
[atguigu@hadoop102 bin]$ vim dws_to_dwt.sh
#!/bin/bash APP=gmall
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="
insert overwrite table ${APP}.dwt_uv_topic
select
nvl(new.mid_id,old.mid_id),
nvl(new.user_id,old.user_id),
nvl(new.version_code,old.version_code),
nvl(new.version_name,old.version_name),
nvl(new.lang,old.lang),
nvl(new.source,old.source),
nvl(new.os,old.os),
nvl(new.area,old.area),
nvl(new.model,old.model),
nvl(new.brand,old.brand),
nvl(new.sdk_version,old.sdk_version),
nvl(new.gmail,old.gmail),
nvl(new.height_width,old.height_width),
nvl(new.app_time,old.app_time),
nvl(new.network,old.network),
nvl(new.lng,old.lng),
nvl(new.lat,old.lat),
nvl(old.login_date_first,'$do_date'),
if(new.login_count>0,'$do_date',old.login_date_last),
nvl(new.login_count,0),
nvl(new.login_count,0)+nvl(old.login_count,0)
from
(
select
*
from ${APP}.dwt_uv_topic
)old
full outer join
(
select
*
from ${APP}.dws_uv_detail_daycount
where dt='$do_date'
)new
on old.mid_id=new.mid_id;
"
$hive -e "$sql"
需求定义:
日活:当日活跃的设备数
周活:当周活跃的设备数
月活:当月活跃的设备数
#注意这里不再是分区表。一天执行一次
hive (gmall)> drop table if exists ads_uv_count;
create external table ads_uv_count
( `dt` string COMMENT '统计日期',
`day_count` bigint COMMENT '当日用户数量',
`wk_count` bigint COMMENT '当周用户数量',
`mn_count` bigint COMMENT '当月用户数量',
`is_weekend` string COMMENT 'Y,N 是否是周末,用于得到本周最终结果',
`is_monthend` string COMMENT 'Y,N 是否是月末,用于得到本月最终结果' )
COMMENT '活跃设备数'
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_uv_count/';
#数据加载
hive (gmall)>
insert into table ads_uv_count
select
'2020-03-10' dt,
daycount.ct,
wkcount.ct,
mncount.ct,
#注意这里写的方式,判断是否是周末,用于得到本周最终结果
if(date_add(next_day('2020-03-10','MO'),-1)='2020-03-10','Y','N') ,
#是否是月末,用于得到本月最终结果
if(last_day('2020-03-10')='2020-03-10','Y','N')
from
(
#统计一天的数据
select
'2020-03-10' dt,
count(*) ct
from dwt_uv_topic
where login_date_last='2020-03-10'
)daycount join
(
#统计一周的数据
select
'2020-03-10' dt,
count (*) ct
from dwt_uv_topic
#注意这里获取到当周,而不是最近一周!
where login_date_last>=date_add(next_day('2020-03-10','MO'),-7)
and login_date_last<= date_add(next_day('2020-03-10','MO'),-1)
) wkcount on daycount.dt=wkcount.dt
join
(
#统计一个月的数据
select
'2020-03-10' dt,
count (*) ct
from dwt_uv_topic
where
date_format(login_date_last,'yyyy-MM')=date_format('2020-03-10','yyyy-MM')
)mncount on daycount.dt=mncount.dt;
对应的固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_uv_count
select '$do_date',
sum(if(login_date_last='$do_date',1,0)), sum(if(login_date_last>=date_add(next_day('$do_date','monday'),-7) and login_date_last<=date_add(next_day('$do_date','monday'),-1) ,1,0)), sum(if(date_format(login_date_last,'yyyy-MM')=date_format('$do_date','yyyy-M M'),1,0)), if('$do_date'=date_add(next_day('$do_date','monday'),-1),'Y','N'), if('$do_date'=last_day('$do_date'),'Y','N')
from dwt_uv_topic;
"
$hive -e "$sql"
hive (gmall)> drop table if exists ads_new_mid_count;
create external table ads_new_mid_count
( `create_date` string comment '创建时间' ,
`new_mid_count` BIGINT comment '新增设备数量' )
COMMENT '每日新增设备信息数量'
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_new_mid_count/';
hive (gmall)>
insert into table ads_new_mid_count
select login_date_first,
count(*) from dwt_uv_topic
where login_date_first='2020-03-10'
group by login_date_first;
需求定义:
沉默用户:只在安装当天启动过,且启动时间是在 7 天前
hive (gmall)>
drop table if exists ads_silent_count;
create external table ads_silent_count(
`dt` string COMMENT '统计日期',
`silent_count` bigint COMMENT '沉默设备数'
)
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_silent_count';
hive (gmall)>
insert into table ads_silent_count
select
'2020-03-15',
count(*)
from dwt_uv_topic
where login_date_first=login_date_last --对应第一点需求:只在安装当天启动过
and login_date_last<=date_add('2020-03-15',-7); --对应第二点需求:启动时间是在 7 天前
对应的固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_silent_count select '$do_date', count(*) from dwt_uv_topic where login_date_first=login_date_last and login_date_last<=date_add('$do_date',-7);
$hive -e "$sql"
需求定义:
本周回流用户:上周未活跃,本周活跃的设备,且不是本周新增设备。
hive (gmall)> drop table if exists ads_back_count;
create external table ads_back_count
( `dt` string COMMENT '统计日期',
`wk_dt` string COMMENT '统计日期所在周',
`wastage_count` bigint COMMENT '回流设备数' )
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_back_count';
#整体思路是本周活跃的设备去left join上周的设备,然后排出上周活跃的设备id,其中本周活跃的社备中用初始登录时间小于这周之前来剔除本周新增的社备。
hive (gmall)> insert into table ads_back_count
select '2020-03-15',
count(*)
from
( select
mid_id
from dwt_uv_topic
where login_date_last>=date_add(next_day('2020-03-15','MO'),-7)
and login_date_last<= date_add(next_day('2020-03-15','MO'),-1)
and login_date_first<date_add(next_day('2020-03-15','MO'),-7) )current_wk --这里剔除掉本周新增的设备
left join
( select
mid_id
from dws_uv_detail_daycount
where dt>=date_add(next_day('2020-03-15','MO'),-7*2)
and dt<= date_add(next_day('2020-03-15','MO'),-7-1)
group by mid_id )last_wk
on current_wk.mid_id=last_wk.mid_id
where last_wk.mid_id is null; --这里剔除掉上周活跃的设备
写成固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_back_count
select
'$do_date',
concat(date_add(next_day('2020-03-10','MO'),-7),'_',date_add(next_day('2020-03-10','MO'),-1)),
count(*)
from
(
select
mid_id
from dwt_uv_topic
where login_date_last>=date_add(next_day('$do_date','MO'),-7)
and login_date_last<= date_add(next_day('$do_date','MO'),-1)
and login_date_first$do_date ','MO'),-7)
)current_wk
left join
(
select
mid_id
from dws_uv_detail_daycount
where dt>=date_add(next_day('$do_date','MO'),-7*2)
and dt<= date_add(next_day('$do_date','MO'),-7-1)
group by mid_id
)last_wk
on current_wk.mid_id=last_wk.mid_id
where last_wk.mid_id is null;
"
$hive -e "$sql"
需求定义:
流失用户:最近 7 天未活跃的设备
hive (gmall)> insert into table ads_wastage_count
select'2020-03-20', count(*)
from ( select
mid_id from dwt_uv_topic
where login_date_last<=date_add('2020-03-20',-7)
这里先算出一天的留存率,其实要说难也不难,但看完代码要学习这种思路。
统计日期应该是跟dt一样的时间,而不是什么时间跑该数据的日期。
像这里求留存数量:sum(if(login_date_first=date_add(‘2020-03-10’,-1) and login_date_last=‘2020-03-10’,1,0))
先判断初始登录时间是否是昨天登录,后判断最后登录时间是否是今天,也就是留存了一天,这样判断出来后,要统计哪天和留存多少天数的留存率,就可以直接对应修改这两个参数。
hive (gmall)> drop table if exists ads_user_retention_day_rate;
create external table ads_user_retention_day_rate
( `stat_date` string comment '统计日期',
`create_date` string comment '设备新增日期',
`retention_day` int comment '截止当前日期留存天数',
`retention_count` bigint comment '留存数量',
`new_mid_count` bigint comment '设备新增数量',
`retention_ratio` decimal(10,2) comment '留存率' )
COMMENT '每日用户留存情况'
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_user_retention_day_rate/';
hive (gmall)> insert into table ads_user_retention_day_rate
select '2020-03-10',--统计日期
date_add('2020-03-10',-1),--新增日期
1,--留存天数
sum(if(login_date_first=date_add('2020-03-10',-1) and login_date_last='2020-03-10',1,0)),--2020-03-09 的 1 日留存数
sum(if(login_date_first=date_add('2020-03-10',-1),1,0)),--2020-03-09 新增 sum(if(login_date_first=date_add('2020-03-10',-1) and login_date_last='2020-03-10',1,0))/sum(if(login_date_first=date_add('2020-03-10',- 1),1,0))*100 --留存率
from dwt_uv_topic;
#类似的统计留存天数是2时:
select '2020-03-10',--统计日期
date_add('2020-03-10',-2),--新增日期
2,--留存天数
sum(if(login_date_first=date_add('2020-03-10',-2) and login_date_last='2020-03-10',1,0)),--2020-03-09 的 1 日留存数
sum(if(login_date_first=date_add('2020-03-10',-2),1,0)),--2020-03-09 新增 sum(if(login_date_first=date_add('2020-03-10',-2) and login_date_last='2020-03-10',1,0))/sum(if(login_date_first=date_add('2020-03-10',- 2),1,0))*100 --留存率
from dwt_uv_topic;
写成固定脚本就如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_user_retention_day_rate
select
'$do_date',
date_add('$do_date',-3),
3,
sum(if(login_date_first=date_add('$do_date',-3) and login_date_last='$do_date',1,0)),
sum(if(login_date_first=date_add('$do_date',-3),1,0)),
sum(if(login_date_first=date_add('$do_date',-3) and login_date_last='$do_date',1,0))/sum(if(login_date_first=date_add('$do_date', -3),1,0))*100
from dwt_uv_topic
union all
select
'$do_date',
date_add('$do_date',-2),
2,
sum(if(login_date_first=date_add('$do_date',-2) and login_date_last='$do_date',1,0)),
sum(if(login_date_first=date_add('$do_date',-2),1,0)),
sum(if(login_date_first=date_add('$do_date',-2) and login_date_last='$do_date',1,0))/sum(if(login_date_first=date_add('$do_date', -2),1,0))*100
from dwt_uv_topic
union all
select
'$do_date',
date_add('$do_date',-1),
1,
sum(if(login_date_first=date_add('$do_date',-1) and login_date_last='$do_date',1,0)),
sum(if(login_date_first=date_add('$do_date',-1),1,0)),
sum(if(login_date_first=date_add('$do_date',-1) and login_date_last='$do_date',1,0))/sum(if(login_date_first=date_add('$do_date', -1),1,0))*100
from dwt_uv_topic;
"
$hive -e "$sql"
注意这里是最近连续三周,而不是让统计3 * 7 =21天都连续登录的用户!还有本周的数据也算,要用next_day判断。
那么就可以分开三周,每周都有登录过的用户,最后count(*) >=3的用户,另一种更直接的方式是三个join直接连接就是三周都登录的,但在大数据中join是比较消耗性能的。
活跃次数是所有的mid加起来。
hive (gmall)> drop table if exists ads_continuity_wk_count;
create external table ads_continuity_wk_count
( `dt` string COMMENT '统计日期,一般用结束周周日日期,如果每天计算一次,可用当天日 期',
`wk_dt` string COMMENT '持续时间',
`continuity_count` bigint COMMENT '活跃次数' )
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_continuity_wk_count';
#写成固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_continuity_wk_count
select
'$do_date',
concat(date_add(next_day('$do_date','MO'),-7*3),'_',date_add(next_day('$do_d
ate','MO'),-1)),
count(*)
from
(
#注意这里如果不套多一层子查询,上面count(*)结果是对每一组mid_id统计,而不是整体所有的统计数
select
mid_id
from
(
select
mid_id
from dws_uv_detail_daycount
where dt>=date_add(next_day('$do_date','monday'),-7)
and dt<=date_add(next_day('$do_date','monday'),-1)
group by mid_id
union all
select
mid_id
from dws_uv_detail_daycount
where dt>=date_add(next_day('$do_date','monday'),-7*2)
and dt<=date_add(next_day('$do_date','monday'),-7-1)
group by mid_id
union all
select
mid_id
from dws_uv_detail_daycount
where dt>=date_add(next_day('$do_date','monday'),-7*3)
and dt<=date_add(next_day('$do_date','monday'),-7*2-1)
group by mid_id
)t1
group by mid_id
#这里直接计算去重后的mid的行数是等于3的就说明是连续三周都有登录的设备
having count(*)=3
)t2;
"
$hive -e "$sql"
思路一:
(1)先找出最近7天的活跃用户
(2)第二步,也是主要的,由于这里的dt日期不会重复,所以用rank()函数给mid和dt排序,然后用将日期减去排序值,如果是连续的日期,那么差值会得到一个相同的值。
(3)取mid和差值相同的,并且条数>=3的mid就是有连续三天活跃的用户数。
hive (gmall)> drop table if exists ads_continuity_uv_count;
create external table ads_continuity_uv_count
( `dt` string COMMENT '统计日期',
`wk_dt` string COMMENT '最近 7 天日期',
`continuity_count` bigint )
COMMENT '连续活跃设备数'
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_continuity_uv_count';
#写成固定脚本如下:
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_continuity_uv_count
select
'$do_date',
concat(date_add('$do_date',-6),'_','$do_date'),
count(*)
from
(
select
mid_id
from
(
select
mid_id
from
(
select
mid_id,
#如果是连续的日期,那么差值会得到一个相同的值
date_sub(dt,rank) date_dif --减函数,这里也可以用date_diff
from
(
select
mid_id,
dt,
rank() over(partition by mid_id order by dt) rank
from dws_uv_detail_daycount
where
dt>=date_add('$do_date',-6) and dt<='$do_date' --最近7天应该减6
)t1
)t2
group by mid_id,date_dif
having count(*)>=3 --取出连续3天的
)t3
group by mid_id #再对mid去重,否则假设mid有两端连续3天的时间段,mid就会重复
)t4;
"
$hive -e "$sql"
思路二:
(1)先找出最近7天的活跃用户
(2)第二步,这里表的dt因为不会重复,如果是在实际生产中有重复的,需要先加工去重。然后用lead()函数取出dt的后两行值.
(3)再把dt和lead函数的结果相减,如果等于2,说明日期是有三行连续的,比如:
mid dt lead(dt,2,'1970-01-01') datediff
1 2020-03-11 2020-03-13 2
1 2020-03-12 2020-03-15 3
1 2020-03-13 2020-03-16 3
1 2020-03-15 2020-03-17 2
1 2020-03-16 1970-01-01 -省略
1 2020-03-17 1970-01-01 -省略
(3)最后直接count 去重后的mid即可。
hive (gmall)> drop table if exists ads_user_topic;
create external table ads_user_topic
( `dt` string COMMENT '统计日期',
`day_users` string COMMENT '活跃会员数',
`day_new_users` string COMMENT '新增会员数',
`day_new_payment_users` string COMMENT '新增消费会员数',
`payment_users` string COMMENT '总付费会员数',
`users` string COMMENT '总会员数',
`day_users2users` decimal(10,2) COMMENT '会员活跃率',
`payment_users2users` decimal(10,2) COMMENT '会员付费率',
`day_new_users2users` decimal(10,2) COMMENT '会员新鲜度' )
COMMENT '会员主题信息表' row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_user_topic';
hive (gmall)>
insert into table ads_user_topic
select '2020-03-10',
sum(if(login_date_last='2020-03-10',1,0)),
sum(if(login_date_first='2020-03-10',1,0)),
sum(if(payment_date_first='2020-03-10',1,0)),
sum(if(payment_count>0,1,0)),
count(*),
sum(if(login_date_last='2020-03-10',1,0))/count(*), --主要是这里,学习这种用法。
sum(if(payment_count>0,1,0))/count(*),
sum(if(login_date_first='2020-03-10',1,0))/sum(if(login_date_last='2020-03-10',1,0))
from dwt_user_topic
首先要知道漏斗分析是什么?
这里是统计:“浏览->购物车->下单->支付”的转化率
思路:统计各个行为的人数,然后计算比值。(购物车/浏览 下单/购物车 支付/ 下单)
hive (gmall)>
drop table if exists ads_user_action_convert_day;
create external table ads_user_action_convert_day(
`dt` string COMMENT '统计日期',
`total_visitor_m_count` bigint COMMENT '总访问人数',
`cart_u_count` bigint COMMENT '加入购物车的人数',
`visitor2cart_convert_ratio` decimal(10,2) COMMENT '访问到加入购物车转化率',
`order_u_count` bigint
COMMENT '下单人数',
`cart2order_convert_ratio` decimal(10,2) COMMENT '加入购物车到下单转化率',
`payment_u_count` bigint
COMMENT '支付人数',
`order2payment_convert_ratio` decimal(10,2) COMMENT '下单到支付的转化率'
) COMMENT '用户行为漏斗分析'
row format delimited fields terminated by '\t'
location '/warehouse/gmall/ads/ads_user_action_convert_day/';
#这里的cast可能不需要强转,因为建表是decimal,在hive中insert插入时会自动转换为decimal
hive (gmall)>
insert into table ads_user_action_convert_day
select
'2020-03-10',
uv.day_count,
ua.cart_count,
cast(ua.cart_count/uv.day_count as decimal(10,2)) visitor2cart_convert_ratio,
ua.order_count,
cast(ua.order_count/ua.cart_count as decimal(10,2)) visitor2order_convert_ratio,
ua.payment_count,
cast(ua.payment_count/ua.order_count as decimal(10,2)) order2payment_convert_ratio
from
(
select
dt,
sum(if(cart_count>0,1,0)) cart_count, --主要是这一步,可以学习一下。
sum(if(order_count>0,1,0)) order_count,
sum(if(payment_count>0,1,0)) payment_count
from dws_user_action_daycount
where dt='2020-03-10'
group by dt
)ua
join ads_uv_count uv
on uv.dt=ua.dt;
#商品销量排名,直接order by 降序
insert into table ads_product_sale_topN
select '2020-03-10' dt, --统计日期
sku_id, --商品id
payment_amount --销量
from dws_sku_action_daycount
where dt='2020-03-10'
order by payment_amount desc limit 10;
#商品收藏排名,直接order by 降序
insert into table ads_product_favor_topN
select '2020-03-10' dt, --统计日期
sku_id, --商品id
favor_count --收藏量
from dws_sku_action_daycount
where dt='2020-03-10'
order by favor_count desc limit 10;
#商品差评率, 差评/好评+中评+差评+默认评价数
insert into table ads_appraise_bad_topN
select '2020-03-10' dt,
sku_id,
appraise_bad_count/(appraise_good_count+appraise_mid_count+appraise_bad_coun t+appraise_default_count) appraise_bad_ratio
from dws_sku_action_daycount
where dt='2020-03-10'
order by appraise_bad_ratio desc limit 10;
用户 + 商品 + 购买行为
GMV:成交总额,通常按照下单金额计算,为了取大一点,可能用于投资招标。
#下单数目统计
#需求分析:统计每日下单数,下单金额及下单用户数。
hive (gmall)> insert into table ads_order_daycount
select '2020-03-10', --统计日期
sum(order_count), --单日下单笔数
sum(order_amount), --单日下单金额
sum(if(order_count>0,1,0)) --单日下单用户数
from dws_user_action_daycount where dt='2020-03-10';
#支付信息统计
#每日支付金额、支付人数、支付商品数、支付笔数以及下单到支付的平均时长(取自 DWD)
#单日支付商品数是指商品类目数,而不是商品数量。
#这里面有两个难点,一个是下单到支付的平均时长怎么取值,要求取分钟数,另一个是时间的运算,怎么得出是分钟数。
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_payment_daycount
select tmp_payment.dt, --统计日期
tmp_payment.payment_count, --单日支付笔数
tmp_payment.payment_amount,--单日支付金额
tmp_payment.payment_user_count, --单日支付人数
tmp_skucount.payment_sku_count, --单日支付商品数
tmp_time.payment_avg_time --下单到支付的平均时长,取分钟数
from (
select '$do_date' dt,
sum(payment_count) payment_count,
sum(payment_amount) payment_amount,
sum(if(payment_count>0,1,0)) payment_user_count
from dws_user_action_daycount
where dt='$do_date'
)tmp_payment
join (
select '$do_date' dt,
sum(if(payment_count>0,1,0)) payment_sku_count
from dws_sku_action_daycount
where dt='$do_date'
)tmp_skucount
on tmp_payment.dt=tmp_skucount.dt
join (
select '$do_date' dt,
sum(unix_timestamp(payment_time)-unix_timestamp(create_time))/count(*)/60 payment_avg_time --这里用到了hive的unix_timestamp函数,将日期转换成时间戳来运算 ,得出平均时长的分钟数。unix_timestamp函数返回的结果是10位的秒数
from dwd_fact_order_info
where dt='$do_date' and payment_time is not null --要取有支付的。
)tmp_time
on tmp_payment.dt=tmp_time.dt;
#复购率
#以品牌复购率为例:第一天购买李宁有100人,第二天这100人中有50人第二次来购买了,那么第二天的复购率就是50%,第三天有20来购买了,那么第三天的复购率就是20%
#一般统计一个月内的
#t
#!/bin/bash
hive=/opt/module/hive/bin/hive
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
sql="use gmall;
insert into table ads_sale_tm_category1_stat_mn
select mn.sku_tm_id, --品牌 id
mn.sku_category1_id, --1 级品类 id
mn.sku_category1_name, --1 级品类名称
sum(if(mn.order_count>=1,1,0)) buycount, --购买人数
sum(if(mn.order_count>=2,1,0)) buyTwiceLast, --两次以上购买人数
sum(if(mn.order_count>=2,1,0))/sum( if(mn.order_count>=1,1,0)) buyTwiceLastRatio, --单次复购率
sum(if(mn.order_count>=3,1,0)) buy3timeLast , --三次以上购买人数
sum(if(mn.order_count>=3,1,0))/sum( if(mn.order_count>=1,1,0)) buy3timeLastRatio , --多次复购率
date_format('$do_date' ,'yyyy-MM') stat_mn, --统计月份
'$do_date' stat_date --统计日期
from ( select user_id,
sd.sku_tm_id,
sd.sku_category1_id,
sd.sku_category1_name,
sum(order_count) order_count --统计该月购买次数
from dws_sale_detail_daycount sd
where date_format(dt,'yyyy-MM')=date_format('$do_date' ,'yyyy-MM')
group by user_id, sd.sku_tm_id, sd.sku_category1_id, sd.sku_category1_name
) mn
group by mn.sku_tm_id, mn.sku_category1_id, mn.sku_category1_name;
"
$hive -e "$sql"
– 待补充
6.5 Azkaban定时调度
第一步:在web service页面创建project
Name:gmall
Description:gmall
Flow Parameters 设置工作流程参数,也可以设置job脚本中$dt这个时间参数,Name 设置dt,Value不做设置,因为job里面的sh脚本中我们写了dt默认是前一天的时间,为空就可以直接跑前一天。
然后查看MySQL中是否有数据。
定时调度后,每天都会生成数据,后续就可以考虑将日活 数据可视化