看板5最后一个看板
作者:互联网
看板4回顾
-
在做分析的时候,所有的表都要考虑到增量的情况
-
维度
- 数据量少,直接覆盖即可
- 数据量大,根据时间来抽取最新的即可
有的表,本质上是实时表,但是可能做维度的作用。
比如看板4 的 itcast_clazz表,是学生的报名信息表。
本质上是一个学生报名某个校区某个学科产生的事实时间,本质上是事实表的属性
但是在看板4中,是维度的作用。
对于这个表,放入DIMEN层,或者ODS层都可以。
做增量的时候,对于
任何有事实属性的表
,都要考虑到它的增量采集问题,也就是- 选择SCD的模式,比如选择SCD2(保留全部的历史状态)
- 选项SCD的实现模式,比如SCD2
1. 通过增加表来实现(每一次采集都产生一个新表)
2. 通过增加列来实现(在表中增加对行数据的有效期做判断)(比如valid_start_from和valid_end_from)
-
事实
考虑到它的增量采集问题,也就是
- 选择SCD的模式,比如选择SCD2(保留全部的历史状态)
- 选项SCD的实现模式,比如SCD2
1. 通过增加表来实现(每一次采集都产生一个新表)
2. 通过增加列来实现(在表中增加对行数据的有效期做判断)(比如valid_start_from和valid_end_from)
-
不是所有的事实表都需要做历史信息的判断处理。
就是指这个事实表中的事件,它的
状态就1个
,也就是生命周期从开始,就结束
了。比如:打卡按指纹考勤,这种事件,从开始,就结束了。因为这个事件不会有状态的变更。只有一个状态就是:几点几分谁打卡了。
Hive的常见函数
IF 函数
语法:
IF (表达式, true结果, false结果)
意义:对表达式进行判断,如果表达式为真
,将返回true的结果
如果为假
将返回false的结果
举例:
SELECT IF (1=1, '是', '否');
-- IF也可以嵌套比如
IF (a=b, IF(c=e, 1, 2), 0);
nvl函数
语法:
NVL(被判断的值, 默认返回值)
意义:对被判断的值进行判断,如果它为NULL,将返回默认值,否则就返回这个值本身
举例:
SELECT NVL(NULL, '是空');
COALESCE函数
帮助查找第一个不为空的数据
语法:
COALESCE(参数列表*);
作用:从参数列表中,返回第一个不为NULL的数据
-- 示例
SELECT COALESCE(NULL,NULL,'你好', '哈哈');
-- 将会返回你好
CASE WHEN THEN
作用就是对数据进行判断,返回想要的值
语法1:
-- 以CASE开始
CASE
WHEN 表达式 THEN 返回值
WHEN 表达式 THEN 返回值
......
ELSE
END -- 以END结束
示例sql
CASE
WHEN name='张三' THEN '帅哥'
WHEN name='李四' THEN '丑男'
ELSE '呵呵'
END
语法2:
-- 以CASE开始
CASE
列
WHEN 值 THEN 返回值
WHEN 值 THEN 返回值
......
ELSE
END -- 以END结束
CASE
name
WHEN ’张三' THEN '帅哥'
ELSE '丑男'
END;
isnull
语法:isnull( a )
返回值:boolean
说明:如果a为null就返回true,否则返回false。
isnotnull
语法:isnotnull(a)
返回值:boolean
说明:如果a为非null就返回true,否则返回false。
Hive优化2
并行优化
并行编译
Hive默认情况下,只能同时编译一个SQL到MapReduce代码的转换,并对这个过程上锁。
为了提高效率,同时减少死锁发生的可能性,我们需要将这个一次只能编译一个的操作,优化为并行执行。
参数:
set hive.driver.parallel.compilation=true;
默认这个参数是False;
搭配参数:
hive.driver.parallel.compilation.global.limit
表示,最大的并行度是多少。默认是3
这个优化不能提交性能,但是能够提高体验
并行Stage执行
Hive的SQL在翻译成MR任务的时候,可能会有很多的stage(阶段)
阶段之间都会有依赖关系:
- 前后依赖(前面的MR执行完成,后面的MR才可以去运行)
- 无依赖
如果多个State之间没有依赖,他们如果能够并行执行就能够提高集群的整体资源利用率。
参数:
set hive.exec.parallel=true,可以开启并发执行,默认为false。
set hive.exec.parallel.thread.number=16; //同一个sql允许的最大并行度,默认为8。
小文件优化
建议在企业中去使用,个人虚拟机电脑的性能不足,会导致性能下降。
前置条件:MapReduce的执行结果,不是一个单独的文件。而是多个文件。
如果MR任务的结果产生了很多的小文件存储在HDFS中,那么就会造成性能的下降(对NameNode的压力会很大)
小文件的HDFS影响
-
对磁盘寻道不友好(排除SSD,这里指的是机械硬盘)
- 95%的HDFS集群底层都是机械硬盘(少量土豪用SSD)
- 机械硬盘在随机读写上的性能是非常弱势的(对比SSD)
- 因为小文件代表了更多的block,更多的block代表了更多的数据并非连续的存储在磁盘的某一区域而是分散存储。
- 因为block多带来的分散存储,导致磁盘会频繁的进行随机寻址。
-
对NameNode的压力很大
在HDFS中文件是以
block
存储的。每一个块在HDFS中都有记录。如果文件较小,并且比较多的话,就导致block的数量会变的更多,更多的block会消耗更多的NameNode的资源。
举例:
磁盘占用率100%,但是每秒传输数据量,不到5MB/s
底层原因就是:小文件太多,磁盘在做频繁的随机寻址
Hive执行MapReduce也会产生很多的结果文件。
这些结果文件,都不一定每一个都达到了一个block的大小。
随着时间的推移,结果文件会越攒越多,最终还是导致了文件过多对HDFS的影响,以及文件都不一定达到block的大小,也是一种小文件过多的问题。
对于这个问题,我们可以要求Hive在执行完毕后,合并结果文件。
合并后不是一个文件,而是可能多个文件。
每一个文件默认是按照HDFS的block大小来设定的
参数
# 是否开启合并Map端小文件,在Map-only的任务结束时合并小文件,true是打开。
hive.merge.mapfiles
# 是否开启合并Reduce端小文件,在map-reduce作业结束时合并小文件。true是打开。
hive.merge.mapredfiles
# 合并后MR输出文件的大小,默认为256M。建议设置为255M
hive.merge.size.per.task
# 对小文件判断的平均大小阈值(结果文件的平均大小如果小于阈值,才会进行合并的操作)
hive.merge.smallfiles.avgsize
矢量化查询
对于分布式系统的优化,有两个方向:
-
增加分布式能力(增加并行计算能力)(横向拓展)
说白了就是加机器,加CPU数量
-
增加单机的处理能力(纵向拓展)
矢量化查询,就是类型2的优化。
MapReduce默认情况下,对数据的处理是一条条的处理的。
一条条处理是很正常的处理方式,写到这里,没有任何歧义,只是为了烘托矢量化查询而已。
矢量化查询是指,对数据一批批的处理。
要执行矢量化是有要求的:
- 必须是
ORC
存储
参数
# 开启矢量化查询
set hive.vectorized.execution.enabled=true;
# 开启矢量化在reduce端
set hive.vectorized.execution.reduce.enabled = true;
这种带有enable类型的,只是指开启某个功能,功能能不能用得上,是hive的判断。
比如前面将的map join
读取零拷贝优化
概念:尽量减少在读取的时候读取的数据量的大小。
条件:
- ORC存储(列存储)
- 查询只用到的部分的列
一般情况下,我们写SQL是有一部分的SQL是只会用到表中的部分的列
,并不是要全部的列。
这种场景下,就可以只加载用到的列即可。不用到的不去加载
(必须是列式存储)。
参数
set hive.exec.orc.zerocopy=true;
依旧是开启功能,能否用上,看hive的判断
数据倾斜优化
数据倾斜:在分布式程序分配任务的时候,任务分配的不平均。
数据倾斜,在企业开发中是经常遇到的,以及是非常影响性能的一种场景。
数据倾斜一旦发生,横向拓展只能缓解这个情况,而不能解决这个情况。
如果遇到数据倾斜,一定要从根本上去解决这个问题。而不是想着加机器来解决。
JOIN的时候的倾斜
方案一
用前面讲过的map join SMB join 这些优化去解决。
效果不太好,本身这些提高执行性能的方案,顺带着将倾斜的性能也提升一点,本质上不是解决倾斜的方案。
方案二
Sekw Join
方案的方式是:
对倾斜列的数据,进行单独处理。也就是遇到倾斜列的数据的时候,直接找一个中间目录临时存储,当前MR不去处理
等当前MR完成后,在单独处理这个倾斜的数据集。
这种解决方式有一个前置条件,Hive必须要知道,哪个列的数据倾斜了。
如何让Hive知晓哪个列是倾斜列,就有2种方式
方式1:运行时判断
在执行MR的过程中,Hive会对数据记录计数器
,当计数器的值大于某个阈值的时候,认为这个数据是倾斜的列,对其进行单独处理。
方式2:编译时判断
指的就是,执行SQL的人提前知晓某个列就是倾斜列。
在建表的时候,就指定某个列是倾斜列即可。
参数
# 开启倾斜优化,针对倾斜优化的总开关
set hive.optimize.skewjoin=true;
# 设置运行时判断的时候,对倾斜数据量的阈值
set hive.skewjoin.key=100000;
# 开启编译时的倾斜优化,针对编译时的开关
set hive.optimize.skewjoin.compiletime=true;
# 示例语句,这个语句用于编译时判断,提前告知Hive哪个列是倾斜的
CREATE TABLE list_bucket_single (key STRING, value STRING)
-- 倾斜的字段和需要拆分的key值
SKEWED BY (id) ON (1,5,6)
-- 为倾斜值创建子目录单独存放
[STORED AS DIRECTORIES];
-- 上面的参数可以组合一块使用
-- 当表有SKEWED BY的设置,走编译时优化
-- 如果表没有这个设置,就运行时优化
在企业场景中,满足编译时的判断的场景不多,多数时候还是靠运行时来优化。
编译时的一种场景举例:
比如,传智播客的北京和上海校区很火爆,90%的学生都来这俩校区。
学生报名的事实表,铁定在北京和上海两个校区的ID上产生倾斜。
这样的场景才是适合编译时的,也就是在干活之前就分析出来哪个地方是倾斜了。
数据倾斜,无法避免,这是事实产生事件的现实映射。
只要你没有能力解决现实事件,那么事件的产生就会倾斜。
我们要做的是,在数据倾斜的前提下,完成性能优化。
Union优化
在前面的优化中,不管是运行时优化,还是编译时优化,都会产生两份结果。
这两份结果最终都需要进行Union操作合并为一份结果
参数:
set hive.optimize.union.remove=true;
开启这个参数的时候,对中间数据进行重复性利用。提升union的性能。
重复利用:不会单独开启任务对多份数据执行合并,而是每一个任务在执行之后直接将结果输出到目的地。
不开启Union合并优化:
MR1 对普通数据进行处理,输入路径:/tmp/1
MR2 对倾斜数据进行处理,输入路径:/tmp/2
合并后,数据写入最终目的地:/user/hive/warehouse/xxx.db/xxxtable/
如果开启了优化:
MR1 对普通数据进行处理,输入路径:/user/hive/warehouse/xxx.db/xxxtable/
MR2 对倾斜数据进行处理,输入路径:/user/hive/warehouse/xxx.db/xxxtable/
GROUP BY分组统计的倾斜处理
对于数据倾斜,典型的两个性能点:
- Join操作
- Group BY操作
分组
必
聚合。
前提条件:对数据走平均打散,不按照hash散列
优化1:
利用MapReduce的Combina 机制,在Map端完成预聚合操作。
因为,分组是必聚合的,所以,我们可以做预聚合
参数:
hive.map.aggr=true;
优化2:
大combina机制,对预聚合产生在第一个MR的reduce端。
最终聚合产生在第二个MR中
将Map端的Combina扩散到真个MR,最终的聚合交由第二个MR来做。
在绝对的性能上:优化1是性能最好的,因为节省了很多的中间数据传输。同时一个MR搞定,不需要搞第二个MR来做。
但是,如果数据量巨大,这个MapReduce的任务的压力就会很大,同时执行时间可能很长。
执行时间过长,中间的变量就不好控。一旦出现问题,重头再来。
所以,对于海量数据一般使用优化2的方式,因为如果出现问题,起码可以从第一阶段的结果再来。
参数:
hive.groupby.skewindata=true;
MapReduce迭代计算的概念(补充)
迭代计算,就是一步步的计算出结果的方式。
方式比一次性计算出结果效率要低,但是稳定性和数据的可复用性
更好。
在很多的企业业务计算中,有的数据计算是很复杂的。
可能:
- 如果要在一个MapReduce中完成这个业务,代码写起来很复杂
- 根本就不可能在一个MapReduce中完成整个业务的计算。
MapReduce的计算模型
上面提到:有可能会:根本就不可能在一个MapReduce中完成整个业务的计算。
这个是因为MapReduce的计算模型,就2个:
- Map模型
- Reduce模型
严格意义来说,MR叫做可供使用的算子就2个,一个是map算子一个是reduce算子
很多的业务计算都受限map和reduce方法的限制,因为,比如map方法
map(传入参数固定){
我们只能在这个部分,做自由处理。
return 返回形式固定
}
reduce(传入参数固定){
我们只能在这个部分,做自由处理。
return 返回形式固定
}
MR的迭代
基于前面的概念,所以很多的业务计算本质上是迭代的计算。
如图,某些复杂的业务场景可能会如图所示执行MR的迭代计算。
上图中,MR之间基于HDFS完成数据的共享。
迭代计算中,中间产生的数据,都是中间结果。
这些中间结果就类似数仓中,ODS->DWD->DWM->DWS->APP的迭代过程。
上图本质上是一个有向无环图(DAG)
有向是指:MR1走向MR6的方向
无环:没有形成闭环
前面分组倾斜处理优化中的优化2方案,就是一种迭代计算的思想延伸。
Hive优化小总结
Hive在各方面优化的东西乱七八糟一堆。
我们这个数仓项目,70%时间都在Hive上,30%的时间在业务分析,建模分析上。
很痛苦,我们只想专心做业务分析,不想搞乱七八糟的这优化那优化的。
后面学习Spark和Flink的时候就能体会到专心做业务的快感了。
看板5需求分析
需求1:
说明:统计指定时间段内,不同班级的出勤人数。
打卡时间在上课前40分钟(否则认为无效)~上课时间点之内,且未早退,则为正常上课打卡。
可以下钻到具体学生的出勤数据。跨天数据直接累加。
指标:出勤人数
粒度:上午、下午、晚自习
条件:年、月、日
数据来源:教学实施与保障系统teach的course_table_upload_detail班级课表、tbh_student_signin_record学生打卡记录表、tbh_class_time_table班级作息时间表。
指标: 出勤人数
维度:
- 日期维度:年、月、日
- 时间维度:上午、下午、晚自习
- 班级维度
- 学生维度
涉及到的信息
表:
- 学生打卡信息表, 得到学生的打卡信息
- 班级的作息时间表 , 确定班级的上下课时间
- 班级课表 用来确定某一天是否是上课日
关联关系
学生打卡和课表的关联
打卡表.class_id = 课表.class_id AND 打卡表.sign_date = 课表.class_date
学生打卡和作息表的关联
打卡表.time_table_id = 作息表.id
需求2 班级出勤率
说明:统计指定时间段内,不同班级的学生出勤率。可以下钻到具体学生的出勤数据。出勤率=出勤人数/当日在读学员人数。
指标:出勤率
维度:年、月、天
粒度:上午、下午、晚自习
条件:年、月
数据来源:教学实施与保障系统的course_table_upload_detail班级课表、tbh_student_signin_record学生打卡记录表、tbh_class_time_table班级作息时间表、class_studying_student_count班级在读学生人数。
指标:
出勤率(出勤人数 , 在读学员人数)
维度:
- 日期维度:年、月、日
- 时间维度:上午、下午、晚自习
- 班级维度
- 学生维度
在读学员人数 通过class_studying_student_count
班级在读人数表来确定,某一天某个班应该
有多少人出勤。
其余的维度和指标 和 需求一一致。
需求分析总结
后续的需求,经过细看,发现,就是要计算:
- 出勤人数(率)
- 迟到人数(率)
- 请假人数(率)
- 旷课人数(率)
- 在读学员人数
维度:
- 日期维度:年月日
- 时间维度:上午、下午、晚自习(基础维度)
- 班级维度
- 学生维度
学生维度要求能够被下钻
也就是对于学生来说,每个学生每一天的出勤状态都应该被统计到。
计算注意点:
-
出勤人数(有打卡记录同时当日是上课日)
-
举例:上午正常出勤
上午上课时间点前40分钟,到上课后10分钟 这个区间内打卡算正常出勤。
-
-
迟到人数(有打卡记录同时当日是上课日,同时又晚于上课时间开始点打卡)
-
举例:上午出勤迟到
上午上课时间点后10分钟开始,到上午上课结束,这个范围内打卡算迟到。
-
-
请假人数
通过学生请假表来判断即可
-
旷课人数
通过 班级总人数 - 请假人数 - 出勤(包含迟到)人数 = 旷课人数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PToKpJcP-1625405131309)(https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/image-20210112091958099.png)]
看板5建模
ODS
表有2个:
- tbh_student_signin_record 学生打卡记录
- student_leave_apply 请假申请表
ods作为原始存储,原样不动采集来就好
学生打卡记录
表存储:ORC
压缩:Zlib、Snappy
分区:日期
请假申请表
存储:ORC
压缩:Zlib、Snappy
分区:日期
增量SCD2?
- 打卡表在增量这里,按照时间拉取过来即可
- 请假申请表,中的请假申请涉及到审批状态的更新,状态更新就设计到SCD的模式选择
DIMEN
- course_table_upload_detail : 班级课表
- class_id和class_date 上索引(布隆)
- class_studying_student_count : 班级在读人数表
- tbh_class_time_table : 班级作息时间表
DWD : 略
DWM
-
维度退化
- 事实表中有2个字段分别是,班级ID和学生ID,如果做退化的话
- 这两个字段可以退化出 班级名称 和学生名称 两个信息
- 由于数据源没有对应的维度表,这个就跳过了
-
预聚合
事实是流水,学生打卡行为记录和请假行为记录。
一天内学生可能会打很多卡,也可能请好几个假
我们不去关心这么多的流水记录,只想聚合得到:
- 学生 上午迟到没,上午请假没
- 学生下午迟到没,下午请假没
- 学生晚上迟到没,晚上请假没
DWM - 1
所以在预聚合阶段,我们要对整个需求的最小维度做聚合
- 日期的最小维度是 日
- 班级的最小维度是学生
- 上午、下午、晚上是固定维度
可以得知,在DWM第一步我们要得到:
能够反映:某日 某学生,上午、下午、晚上的出勤状态表
DWM - 2
在上面的基础上,我们得到了学生和日 维度的聚合结果
还需要向上聚合,得到班级和日的聚合结果
可以得到的表能够反映:
某日,某班级,上午、下午、晚上的出勤人数
某日,某班级,上午、下午、晚上的迟到人数
某日,某班级,上午、下午、晚上的请假人数
某日,某班级,上午、下午、晚上的旷课人数
如上,对班级做聚合得到4个表,每个表都记录了某日 某班级 上午、下午、晚上在4个指标上的量化结果。
经过DWM的两个阶段操作,我们最终得到了:
某日、某班级在上午、下午、晚上的4个方向的指标量化记录
DWS
DWS - 1
在DWM中已经得到
- 在班级维度
- 在日级别维度
的聚合结果
在DWS在这个基础上,做月聚合,和年聚合即可
这样DWS最终可以得到
某年 某班级 上午、下午、晚上 在4个指标方向上的记录
某年某月 某班级 上午、下午、晚上 在4个指标方向上的记录
某年某月某日 某班级 上午、下午、晚上 在4个指标方向上的记录
年 | 月 | 日 | 日期 | 班级id | 上午出勤人数 | 下午出勤人数 | 晚上出勤人数 | 上午请假人数 | 下午请假人数 | 晚上请假人数 | 上午迟到人数 | 下午迟到人数 | 晚上迟到人数 | 上午旷课人数 | 下午旷课人数 | 晚上旷课人数 | 应出勤人数 | 时间类型 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2020 | -1 | -1 | NA | 1 | 2000 | 2001 | 2003 | 2000 | 2001 | 2003 | 2000 | 2001 | 2003 | 2000 | 2001 | 2003 | 12000 | 1 年 2 月 3 日 |
2020 | 11 | -1 | NA | 1 | 150 | 160 | 155 | 150 | 160 | 155 | 150 | 160 | 155 | 150 | 160 | 155 | 3000 | |
2020 | 11 | 1 | 1 | 5 | 7 | 9 | 5 | 7 | 9 | 5 | 7 | 9 | 5 | 7 | 9 | 100 |
看板5建模分析总结
整个建模的核心流程是:
从小到大
- 从各个维度的最小粒度开始做预聚合(DWM-1)(学生级别的维度和日级别的维度)
- 慢慢的向上提升维度(DWM-2,从学生提升到了班级级别)(日期级别还是日级别)
- 继续向上,从日提升到月,从月提升到年(DWS - 1)
- 继续计算,得到需要的各种
率
(DWS - 2)
从小粒度慢慢聚合到大粒度,是数仓建模中一种很常见的指导方式
看板5 建表
ODS
2张表:
学生的打卡表:
set hive.exec.orc.compression.strategy=COMPRESSION;
CREATE TABLE IF NOT EXISTS itcast_ods.student_signin_ods (
id int,
normal_class_flag int comment '是否正课 1 正课 2 自习 3 休息',
time_table_id int comment '作息时间id normal_class_flag=2 关联tbh_school_time_table 或者 normal_class_flag=1 关联 tbh_class_time_table',
class_id int comment '班级id',
student_id int comment '学员id',
signin_time String comment '签到时间',
signin_date String comment '签到日期',
inner_flag int comment '内外网标志 0 外网 1 内网',
signin_type int comment '签到类型 1 心跳打卡 2 老师补卡 3 直播打卡',
share_state int comment '共享屏幕状态 0 否 1是 在上午或下午段有共屏记录,则该段所有记录该字段为1,内网默认为1 外网默认为0 (暂不用)',
inner_ip String comment '内网ip地址',
create_time String comment '创建时间')
comment '学生打卡记录表'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY','orc.bloom.filter.columns'='time_table_id,class_id,signin_date,share_state');
学生请假表:
set hive.exec.orc.compression.strategy=COMPRESSION;
CREATE TABLE IF NOT EXISTS itcast_ods.student_leave_apply_ods (
id int,
class_id int comment '班级id',
student_id int comment '学员id',
audit_state int comment '审核状态 0 待审核 1 通过 2 不通过',
audit_person int comment '审核人',
audit_time String comment '审核时间',
audit_remark String comment '审核备注',
leave_type int comment '请假类型 1 请假 2 销假 (查询是否请假不用过滤此类型,通过有效状态来判断)',
leave_reason int comment '请假原因 1 事假 2 病假',
begin_time String comment '请假开始时间',
begin_time_type int comment '1:上午 2:下午 3:晚自习',
end_time String comment '请假结束时间',
end_time_type int comment '1:上午 2:下午 3:晚自习',
days float comment '请假/已休天数',
cancel_state int comment '撤销状态 0 未撤销 1 已撤销',
cancel_time String comment '撤销时间',
old_leave_id int comment '原请假id,只有leave_type =2 销假的时候才有',
leave_remark String comment '请假/销假说明',
valid_state int comment '是否有效(0:无效 1:有效)',
create_time String comment '创建时间')
comment '学生请假申请表'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY','orc.bloom.filter.columns'='class_id,audit_state,cancel_state,valid_state');
DIMEN
三个表
-- 在读学生人数表
CREATE TABLE IF NOT EXISTS itcast_dimen.class_studying_student_count_dimen (
id int,
school_id int comment '校区id',
subject_id int comment '学科id',
class_id int comment '班级id',
studying_student_count int comment '在读班级人数',
studying_date STRING comment '在读日期')
comment '在读班级的每天在读学员人数'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY','orc.bloom.filter.columns'='studying_student_count,studying_date');
-- 课表
CREATE TABLE IF NOT EXISTS itcast_dimen.course_table_upload_detail_dimen
(
id int comment 'id',
base_id int comment '课程主表id',
class_id int comment '班级id',
class_date STRING comment '上课日期',
content STRING comment '课程内容',
teacher_id int comment '老师id',
teacher_name STRING comment '老师名字',
job_number STRING comment '工号',
classroom_id int comment '教室id',
classroom_name STRING comment '教室名称',
is_outline int comment '是否大纲 0 否 1 是',
class_mode int comment '上课模式 0 传统全天 1 AB上午 2 AB下午 3 线上直播',
is_stage_exam int comment '是否阶段考试(0:否 1:是)',
is_pay int comment '代课费(0:无 1:有)',
tutor_teacher_id int comment '晚自习辅导老师id',
tutor_teacher_name STRING comment '辅导老师姓名',
tutor_job_number STRING comment '晚自习辅导老师工号',
is_subsidy int comment '晚自习补贴(0:无 1:有)',
answer_teacher_id int comment '答疑老师id',
answer_teacher_name STRING comment '答疑老师姓名',
answer_job_number STRING comment '答疑老师工号',
remark STRING comment '备注',
create_time STRING comment '创建时间')
comment '班级课表明细表'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY','orc.bloom.filter.columns'='class_id,class_date');
-- 作息时间表
CREATE TABLE IF NOT EXISTS itcast_dimen.class_time_dimen (
id int,
class_id int comment '班级id',
morning_template_id int comment '上午出勤模板id',
morning_begin_time STRING comment '上午开始时间',
morning_end_time STRING comment '上午结束时间',
afternoon_template_id int comment '下午出勤模板id',
afternoon_begin_time STRING comment '下午开始时间',
afternoon_end_time STRING comment '下午结束时间',
evening_template_id int comment '晚上出勤模板id',
evening_begin_time STRING comment '晚上开始时间',
evening_end_time STRING comment '晚上结束时间',
use_begin_date STRING comment '使用开始日期',
use_end_date STRING comment '使用结束日期',
create_time STRING comment '创建时间',
create_person int comment '创建人',
remark STRING comment '备注')
comment '班级作息时间表'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY','orc.bloom.filter.columns'='id,class_id');
DWM建表
-- DWM - 1,学生维度 + 日级别维度的 最细粒度聚合表
CREATE TABLE IF NOT EXISTS itcast_dwm.student_attendance_dwm (
dateinfo String comment '日期',
class_id int comment '班级id',
student_id int comment '学员id',
morning_att String comment '上午出勤情况:0.正常出勤、1.迟到、2.其他(请假+旷课)',
afternoon_att String comment '下午出勤情况:0.正常出勤、1.迟到、2.其他(请假+旷课)',
evening_att String comment '晚自习出勤情况:0.正常出勤、1.迟到、2.其他(请假+旷课)')
comment '学生出勤(正常出勤和迟到)数据'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 班级出勤表,将上面的表的学生聚合为班级,日期维度还是日级别不变
CREATE TABLE IF NOT EXISTS itcast_dwm.class_attendance_dwm (
dateinfo String comment '日期',
class_id int comment '班级id',
morning_att_count String comment '上午出勤人数',
afternoon_att_count String comment '下午出勤人数',
evening_att_count String comment '晚自习出勤人数',
morning_late_count String comment '上午迟到人数',
afternoon_late_count String comment '下午迟到人数',
evening_late_count String comment '晚自习迟到人数')
comment '学生出勤(正常出勤和迟到)数据'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 班级请假表
CREATE TABLE IF NOT EXISTS itcast_dwm.class_leave_dwm (
dateinfo String comment '日期',
class_id int comment '班级id',
morning_leave_count String comment '上午请假人数',
afternoon_leave_count String comment '下午请假人数',
evening_leave_count String comment '晚自习请假人数')
comment '班级请假数据统计'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 班级旷课表
CREATE TABLE IF NOT EXISTS itcast_dwm.class_truant_dwm (
dateinfo String comment '日期',
class_id int comment '班级id',
morning_truant_count String comment '上午旷课人数',
afternoon_truant_count String comment '下午旷课人数',
evening_truant_count String comment '晚自习旷课人数')
comment '班级请假数据统计'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY');
DWS
-- DWS按照
CREATE TABLE IF NOT EXISTS itcast_dws.class_attendance_dws (
date_str String comment '日期字符串',
class_id int comment '班级id',
studying_student_count int comment '在读班级人数',
morning_att_count String comment '上午出勤人数',
morning_att_ratio String comment '上午出勤率',
afternoon_att_count String comment '下午出勤人数',
afternoon_att_ratio String comment '下午出勤率',
evening_att_count String comment '晚自习出勤人数',
evening_att_ratio String comment '晚自习出勤率',
morning_late_count String comment '上午迟到人数',
morning_late_ratio String comment '上午迟到率',
afternoon_late_count String comment '下午迟到人数',
afternoon_late_ratio String comment '下午迟到率',
evening_late_count String comment '晚自习迟到人数',
evening_late_ratio String comment '晚自习迟到率',
morning_leave_count String comment '上午请假人数',
morning_leave_ratio String comment '上午请假率',
afternoon_leave_count String comment '下午请假人数',
afternoon_leave_ratio String comment '下午请假率',
evening_leave_count String comment '晚自习请假人数',
evening_leave_ratio String comment '晚自习请假率',
morning_truant_count String comment '上午旷课人数',
morning_truant_ratio String comment '上午旷课率',
afternoon_truant_count String comment '下午旷课人数',
afternoon_truant_ratio String comment '下午旷课率',
evening_truant_count String comment '晚自习旷课人数',
evening_truant_ratio String comment '晚自习旷课率',
time_type STRING COMMENT '聚合时间类型:1、按小时聚合;2、按天聚合;3、按周聚合;4、按月聚合;5、按年聚合。'
)
comment '班级请假数据统计'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- DWS - 2
CREATE TABLE IF NOT EXISTS itcast_app.class_attendance_app (
dateinfo String comment '日期',
class_id int comment '班级id',
studying_student_count int comment '在读班级人数',
morning_att_count String comment '上午出勤人数',
morning_att_ratio String comment '上午出勤率',
afternoon_att_count String comment '下午出勤人数',
afternoon_att_ratio String comment '下午出勤率',
evening_att_count String comment '晚自习出勤人数',
evening_att_ratio String comment '晚自习出勤率',
morning_late_count String comment '上午迟到人数',
morning_late_ratio String comment '上午迟到率',
afternoon_late_count String comment '下午迟到人数',
afternoon_late_ratio String comment '下午迟到率',
evening_late_count String comment '晚自习迟到人数',
evening_late_ratio String comment '晚自习迟到率',
morning_leave_count String comment '上午请假人数',
morning_leave_ratio String comment '上午请假率',
afternoon_leave_count String comment '下午请假人数',
afternoon_leave_ratio String comment '下午请假率',
evening_leave_count String comment '晚自习请假人数',
evening_leave_ratio String comment '晚自习请假率',
morning_truant_count String comment '上午旷课人数',
morning_truant_ratio String comment '上午旷课率',
afternoon_truant_count String comment '下午旷课人数',
afternoon_truant_ratio String comment '下午旷课率',
evening_truant_count String comment '晚自习旷课人数',
evening_truant_ratio String comment '晚自习旷课率',
time_type STRING COMMENT '聚合时间类型:1、按小时聚合;2、按天聚合;3、按周聚合;4、按月聚合;5、按年聚合。')
comment '班级请假数据统计'
PARTITIONED BY (yearinfo STRING, monthinfo STRING, dayinfo STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
stored as orcfile
TBLPOPERTIES ('orc.compress'='SNAPPY');
HIVE提到的所有优化项大全
--分区
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.max.dynamic.partitions.pernode=10000;
set hive.exec.max.dynamic.partitions=100000;
set hive.exec.max.created.files=150000;
--hive压缩
set hive.exec.compress.intermediate=true;
set hive.exec.compress.output=true;
--写入时压缩生效
set hive.exec.orc.compression.strategy=COMPRESSION;
--分桶
set hive.enforce.bucketing=true;
set hive.enforce.sorting=true;
set hive.optimize.bucketmapjoin = true;
set hive.auto.convert.sortmerge.join=true;
set hive.auto.convert.sortmerge.join.noconditionaltask=true;
--并行执行
set hive.exec.parallel=true;
set hive.exec.parallel.thread.number=8;
--小文件合并
-- set mapred.max.split.size=2147483648;
-- set mapred.min.split.size.per.node=1000000000;
-- set mapred.min.split.size.per.rack=1000000000;
--矢量化查询
set hive.vectorized.execution.enabled=true;
--关联优化器
set hive.optimize.correlation=true;
--读取零拷贝
set hive.exec.orc.zerocopy=true;
--join数据倾斜
set hive.optimize.skewjoin=true;
-- set hive.skewjoin.key=100000;
set hive.optimize.skewjoin.compiletime=true;
set hive.optimize.union.remove=true;
-- group倾斜
set hive.groupby.skewindata=false;
看板5全量操作
业务数据库 -> ODS 和 DIM
-- 打卡表
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from tbh_student_signin_record where $CONDITIONS' \
--hcatalog-database itcast_ods \
--hcatalog-table student_signin_ods \
-m 6 \
--split-by id
-- 请假表
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from student_leave_apply where $CONDITIONS' \
--hcatalog-database itcast_ods \
--hcatalog-table student_leave_apply_ods \
-m 6 \
--split-by id
-- 班级课表:
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from course_table_upload_detail where $CONDITIONS' \
--hcatalog-database itcast_dimen \
--hcatalog-table course_table_upload_detail_dimen \
-m 2 \
--split-by id
-- 应出勤总人数表:
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from class_studying_student_count where $CONDITIONS' \
--hcatalog-database itcast_dimen \
--hcatalog-table class_studying_student_count_dimen \
-m 2 \
--split-by id
-- 作息时间表
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from tbh_class_time_table where $CONDITIONS' \
--hcatalog-database itcast_dimen \
--hcatalog-table class_time_dimen \
-m 1 \
--split-by id
ODS -> DWM
生成DWM - 1 学生级别 + 日级别的聚合表,需要用到:
-
打卡信息表,记录学生的打卡时间(计算正常出勤和迟到)
-
作息时间表,用来确定学生的上下课时间
关联条件:打卡表.time_table_id = 作息表.id
-
课表,用来确定上课日
关联条件:打卡表.class_id = 课表.class_id AND 打卡表.signin_date = 课表.class_date
过滤条件:对于课表如何确定哪天是有课,条件是:
NVL(content, '') != '' AND content != '开班典礼'
-
聚合分组条件:
- 学生级别:按照班级+学生id分组
- 日级别:按照签到日期分组
- 最终:GROUP BY date, class_id, student_id
SELECT
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd JOIN ON itcast_ods.student_signin_ods AS sso
ON sso.class_id = cutdd.class_id AND sso.signin_date = cutdd.class_date
正常出勤和迟到的,关联条件: 学生打卡记录表.class_id = 课程表.class_id 班级作息时间表.id = 学生打卡记录表.time_table_id
-- 注意点 判断课程表的时候,要注意 WHERE 课程表.content IS NOT NULL AND 课程表.content != '开班典礼' -- 可以放入子查询中优化
生成学生维度和日维度的每个学生每日出勤状态表
-- 动态分区和写入压缩必须开启,从上面的hive优化大全中抽取
INSERT INTO TABLE itcast_dwm.student_attendance_dwm partition(yearinfo, monthinfo, dayinfo)
SELECT
ctudd.class_date AS dateinfo,
sso.class_id,
sso.student_id,
IF(
-- 计算是否出勤
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.morning_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.morning_end_time), 'yyyy-MM-dd HH:mm:ss')
,1
,0
)) > 0 -- 只要大于0,满足出勤了
,
-- 在计算你这个出勤是正常出勤还是迟到出勤,正常返回0,迟到返回1,
-- 既然出勤了,就要知道是正常出勤还是迟到出勤
IF(
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.morning_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.morning_begin_time), 'yyyy-MM-dd HH:mm:ss') + 600
,1, -- 表示正常
0 -- 表示迟到
)) > 0,
0,
1
)
,2 -- 表示不在出勤计算内,返回2
) AS morning_att,
IF(
-- 计算是否出勤
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.afternoon_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.afternoon_end_time), 'yyyy-MM-dd HH:mm:ss')
,1
,0
)) > 0 -- 只要大于0,满足出勤了
,
-- 在计算你这个出勤是正常出勤还是迟到出勤,正常返回0,迟到返回1,
-- 既然出勤了,就要知道是正常出勤还是迟到出勤
IF(
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.afternoon_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.afternoon_begin_time), 'yyyy-MM-dd HH:mm:ss') + 600
,1, -- 表示正常
0 -- 表示迟到
)) > 0,
0,
1
)
,2 -- 表示不在出勤计算内,返回2
) AS afternoon_att,
IF(
-- 计算是否出勤
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.evening_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.evening_end_time), 'yyyy-MM-dd HH:mm:ss')
,1
,0
)) > 0 -- 只要大于0,满足出勤了
,
-- 在计算你这个出勤是正常出勤还是迟到出勤,正常返回0,迟到返回1,
-- 既然出勤了,就要知道是正常出勤还是迟到出勤
IF(
SUM(
IF(UNIX_TIMESTAMP(signin_time, 'yyyy-MM-dd HH:mm:ss') BETWEEN UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.evening_begin_time), 'yyyy-MM-dd HH:mm:ss') - 2400 AND UNIX_TIMESTAMP(CONCAT(signin_date, ' ', ctd.evening_begin_time), 'yyyy-MM-dd HH:mm:ss') + 600
,1, -- 表示正常
0 -- 表示迟到
)) > 0,
0,
1
)
,2 -- 表示不在出勤计算内,返回2
) AS evening_att,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd JOIN itcast_ods.student_signin_ods AS sso
ON sso.class_id = ctudd.class_id AND sso.signin_date = ctudd.class_date
JOIN itcast_dimen.class_time_dimen AS ctd ON sso.time_table_id = ctd.id
GROUP BY ctudd.class_date, sso.class_id, sso.student_id;
统计班级正常出勤和迟到
- 统计这个表,需要的日期,以课表为准
insert into table itcast_dwm.class_attendance_dwm partition(yearinfo,monthinfo,dayinfo)
SELECT
ctudd.class_date AS dateinfo,
sad.class_id,
COUNT( CASE WHEN morning_att != 2 THEN 1 ELSE NULL END) AS morning_att_count,
COUNT( CASE WHEN afternoon_att != 2 THEN 1 ELSE NULL END) AS afternoon_att_count,
COUNT( CASE WHEN evening_att != 2 THEN 1 ELSE NULL END) AS evening_att_count,
COUNT( CASE WHEN morning_att = 1 THEN 1 ELSE NULL END) AS morning_late_count,
COUNT( CASE WHEN afternoon_att = 1 THEN 1 ELSE NULL END) AS afternoon_late_count,
COUNT( CASE WHEN evening_att = 1 THEN 1 ELSE NULL END) AS evening_late_count,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd LEFT JOIN itcast_dwm.student_attendance_dwm AS sad
ON ctudd.class_id = sad.class_id AND ctudd.class_date = sad.dateinfo
GROUP BY ctudd.class_date, sad.class_id;
班级请假人数统计
用到的表:
-
课表作为日期的主体线索
-
请假表,统计学生请假
-
作息表,计算学生是上午、还是下午、还是晚上请假
-
课表和请假表的关联:班级id,课程日期
课程必须是上课日
-
请假表和作息表的关联:班级ID
作息时间在生效范围内
-
-- 插入一条示例数据,因为源数据中没人请假呢
INSERT INTO itcast_ods.student_leave_apply_ods PARTITION (dt)
values (125, 5032, 119142, 1, 3491, '2019-09-08 16:42:29', '出门约会', 1, 2, '2019-09-03 08:00:00', 2, '2019-09-03 23:30:00', 2, 1, 0, null, null, '相亲啊,脱单就看这个了', 1, '2019-09-02 08:56:54','2020-07-07');
INSERT INTO TABLE itcast_dwm.class_leave_dwm PARTITION(yearinfo,monthinfo,dayinfo)
SELECT
t1.dateinfo,
t1.class_id,
t1.morning_leave_count,
t2.afternoon_leave_count,
t3.evening_leave_count,
t1.yearinfo,
t1.monthinfo,
t1.dayinfo
FROM
(
SELECT
ctudd.class_date AS dateinfo,
sla.class_id,
COUNT(DISTINCT sla.student_id) AS morning_leave_count,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd LEFT JOIN (select * from itcast_ods.student_leave_apply_ods where audit_state = 1 and cancel_state = 0 and valid_state =1) AS sla
ON ctudd.class_id = sla.class_id
LEFT JOIN itcast_dimen.class_time_dimen AS ctd ON sla.class_id = ctd.class_id
WHERE SUBSTRING(sla.begin_time, 1, 10) >= ctd.use_begin_date AND SUBSTRING(sla.begin_time, 1, 10) <= ctd.use_end_date AND SUBSTRING(sla.begin_time, 12, 8) <= ctd.morning_begin_time AND SUBSTRING(sla.end_time, 12, 8) >= ctd.morning_end_time
GROUP BY ctudd.class_date, sla.class_id
) AS t1 JOIN
(
SELECT
ctudd.class_date AS dateinfo,
sla.class_id,
COUNT(DISTINCT sla.student_id) AS afternoon_leave_count,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd LEFT JOIN (select * from itcast_ods.student_leave_apply_ods where audit_state = 1 and cancel_state = 0 and valid_state =1) AS sla
ON ctudd.class_id = sla.class_id
LEFT JOIN itcast_dimen.class_time_dimen AS ctd ON sla.class_id = ctd.class_id
WHERE SUBSTRING(sla.begin_time, 1, 10) >= ctd.use_begin_date AND SUBSTRING(sla.begin_time, 1, 10) <= ctd.use_end_date AND
SUBSTRING(sla.begin_time, 12, 8) <= ctd.afternoon_begin_time AND SUBSTRING(sla.end_time, 12, 8) >= ctd.afternoon_end_time
GROUP BY ctudd.class_date, sla.class_id
) AS t2 ON t1.dateinfo = t2.dateinfo AND t1.class_id = t2.class_id JOIN
(
SELECT
ctudd.class_date AS dateinfo,
sla.class_id,
COUNT(DISTINCT sla.student_id) AS evening_leave_count,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (SELECT * FROM itcast_dimen.course_table_upload_detail_dimen WHERE NVL(content, '') != '' AND content != '开班典礼') AS ctudd LEFT JOIN (select * from itcast_ods.student_leave_apply_ods where audit_state = 1 and cancel_state = 0 and valid_state =1) AS sla
ON ctudd.class_id = sla.class_id
LEFT JOIN itcast_dimen.class_time_dimen AS ctd ON sla.class_id = ctd.class_id
WHERE SUBSTRING(sla.begin_time, 1, 10) >= ctd.use_begin_date AND SUBSTRING(sla.begin_time, 1, 10) <= ctd.use_end_date AND
SUBSTRING(sla.begin_time, 12, 8) <= ctd.evening_begin_time AND SUBSTRING(sla.end_time, 12, 8) >= ctd.evening_end_time
GROUP BY ctudd.class_date, sla.class_id
) AS t3 ON t3.dateinfo = t2.dateinfo AND t3.class_id = t2.class_id;
旷课人数统计
- 以课表的上课日为日期主线索
- 拿到总人数,应出勤人数表
- 总人数 - 出勤人数 - 请假人数 = 旷课人数
- 需要的表:
- 总人数记录表
- 课表
- 班级出勤表
- 班级请假表
- 关联条件:
- 课表和总人数表:班级ID和日期
- 课表和出勤表和请假表的关联,按照班级ID和日期
INSERT INTO TABLE itcast_dwm.class_truant_dwm PARTITION(yearinfo,monthinfo,dayinfo)
SELECT
ctudd.class_date AS dateinfo,
ctudd.class_id,
(cssc.studying_student_count - NVL(cad.morning_att_count, 0) - NVL(cld.morning_leave_count, 0)) AS morning_truant_count,
(cssc.studying_student_count - NVL(cad.afternoon_att_count, 0) - NVL(cld.afternoon_leave_count, 0)) AS afternoon_truant_count,
(cssc.studying_student_count - NVL(cad.evening_att_count, 0) - NVL(cld.evening_leave_count, 0)) AS evening_truant_count,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
SUBSTRING(ctudd.class_date, 9, 2) AS dayinfo
FROM (select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd LEFT JOIN (select * from itcast_dimen.class_studying_student_count_dimen where studying_student_count is not null ) cssc on ctudd.class_id = cssc.class_id AND ctudd.class_date = cssc.studying_date LEFT JOIN itcast_dwm.class_attendance_dwm AS cad ON ctudd.class_id = cad.class_id AND ctudd.class_date = cad.dateinfo
LEFT JOIN itcast_dwm.class_leave_dwm AS cld ON ctudd.class_id = cld.class_id AND ctudd.class_date = cld.dateinfo;
insert into table itcast_dwm.class_truant_dwm partition(yearinfo,monthinfo,dayinfo)
select
ctudd.class_date as dateinfo,
ctudd.class_id,
-- 计算要排除NULL,一个NULL存在,全部为NULL
cssc.studying_student_count - nvl(cad.morning_att_count,0) - nvl(cld.morning_leave_count,0) as morning_truant_count,
cssc.studying_student_count - nvl(cad.afternoon_att_count,0) - nvl(cld.afternoon_leave_count,0) as afternoon_truant_count,
cssc.studying_student_count - nvl(cad.evening_att_count,0) - nvl(cld.evening_leave_count,0) as evening_truant_count,
cad.yearinfo,cad.monthinfo,cad.dayinfo
from
-- 以课程表为主,确保有课这一天作为主导。。。。。排除content为NULL和开班典礼
(select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
-- 人数表和课表关联除了班级ID还有日期别忘了关联
left join (select * from itcast_dimen.class_studying_student_count_dimen where studying_student_count is not null ) cssc on cssc.class_id = ctudd.class_id and cssc.studying_date = ctudd.class_date
left join itcast_dwm.class_attendance_dwm cad on ctudd.class_date = cad.dateinfo and ctudd.class_id = cad.class_id
left join itcast_dwm.class_leave_dwm cld on ctudd.class_date = cld.dateinfo and ctudd.class_id = cld.class_id ;
DWM -> DWS
DWS
-- 天维度
INSERT INTO TABLE itcast_dws.class_attendance_dws PARTITION(yearinfo, monthinfo, dayinfo)
SELECT
ctudd.class_date,
ctudd.class_id,
cssc.studying_student_count,
cad.morning_att_count,
CONCAT(CAST((cad.morning_att_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_att_ratio,
cad.afternoon_att_count,
CONCAT(CAST((cad.afternoon_att_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_att_ratio,
cad.evening_att_count,
CONCAT(CAST((cad.evening_att_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_att_ratio,
cad.morning_late_count,
CONCAT(CAST((cad.morning_late_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_late_ratio,
cad.afternoon_late_count,
CONCAT(CAST((cad.afternoon_late_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_late_ratio,
cad.evening_late_count,
CONCAT(CAST((cad.evening_late_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_late_ratio,
cld.morning_leave_count,
CONCAT(CAST((cld.morning_leave_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_leave_ratio,
cld.afternoon_leave_count,
CONCAT(CAST((cld.afternoon_leave_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_leave_ratio,
cld.evening_leave_count,
CONCAT(CAST((cld.evening_leave_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_leave_ratio,
ctd.morning_truant_count,
CONCAT(CAST((ctd.morning_truant_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_truant_ratio,
ctd.afternoon_truant_count,
CONCAT(CAST((ctd.afternoon_truant_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_truant_ratio,
ctd.evening_truant_count,
CONCAT(CAST((ctd.evening_truant_count / cssc.studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_truant_ratio,
'2' AS time_type,
SUBSTRING(cad.dateinfo, 1, 4) AS yearinfo,
SUBSTRING(cad.dateinfo, 6, 2) AS monthinfo,
SUBSTRING(cad.dateinfo, 9, 2) AS dayinfo
FROM (select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
LEFT JOIN itcast_dwm.class_attendance_dwm AS cad ON ctudd.class_id = cad.class_id AND ctudd.class_date = cad.dateinfo
LEFT JOIN itcast_dwm.class_leave_dwm AS cld
ON cad.dateinfo = cld.dateinfo AND cad.class_id = cld.class_id LEFT JOIN itcast_dwm.class_truant_dwm AS ctd
ON cad.dateinfo = ctd.dateinfo AND cad.class_id = ctd.class_id LEFT JOIN (select * from itcast_dimen.class_studying_student_count_dimen where studying_student_count is not null ) cssc
ON cssc.class_id = cad.class_id AND cssc.studying_date = cad.dateinfo;
-- 月维度
INSERT INTO TABLE itcast_dws.class_attendance_dws PARTITION(yearinfo, monthinfo, dayinfo)
SELECT
CONCAT(SUBSTRING(ctudd.class_date, 1, 4), '-', SUBSTRING(ctudd.class_date, 6, 2)) AS dateinfo,
ctudd.class_id,
SUM(cssc.studying_student_count) AS studying_student_count,
SUM(cad.morning_att_count) AS morning_att_count,
CONCAT(CAST((morning_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_att_ratio,
SUM(cad.afternoon_att_count) AS afternoon_att_count,
CONCAT(CAST((afternoon_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_att_ratio,
SUM(cad.evening_att_count) AS evening_att_count,
CONCAT(CAST((evening_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_att_ratio,
SUM(cad.morning_late_count) AS morning_late_count,
CONCAT(CAST((morning_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_late_ratio,
SUM(cad.afternoon_late_count) AS afternoon_late_count,
CONCAT(CAST((afternoon_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_late_ratio,
SUM(cad.evening_late_count) AS evening_late_count,
CONCAT(CAST((evening_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_late_ratio,
SUM(cld.morning_leave_count) AS morning_leave_count,
CONCAT(CAST((morning_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_leave_ratio,
SUM(cld.afternoon_leave_count) AS afternoon_leave_count,
CONCAT(CAST((afternoon_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_leave_ratio,
SUM(cld.evening_leave_count) AS evening_leave_count,
CONCAT(CAST((evening_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_leave_ratio,
SUM(ctd.morning_truant_count) AS morning_truant_count,
CONCAT(CAST((morning_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_truant_ratio,
SUM(ctd.afternoon_truant_count) AS afternoon_truant_count,
CONCAT(CAST((afternoon_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_truant_ratio,
SUM(ctd.evening_truant_count) AS evening_truant_count,
CONCAT(CAST((evening_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_truant_ratio,
'4' AS time_type,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
SUBSTRING(ctudd.class_date, 6, 2) AS monthinfo,
'-1' AS dayinfo
FROM (select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
LEFT JOIN itcast_dwm.class_attendance_dwm AS cad ON ctudd.class_id = cad.class_id AND ctudd.class_date = cad.dateinfo
LEFT JOIN itcast_dwm.class_leave_dwm AS cld
ON cad.dateinfo = cld.dateinfo AND cad.class_id = cld.class_id LEFT JOIN itcast_dwm.class_truant_dwm AS ctd
ON cad.dateinfo = ctd.dateinfo AND cad.class_id = ctd.class_id LEFT JOIN (select * from itcast_dimen.class_studying_student_count_dimen where studying_student_count is not null ) cssc
ON cssc.class_id = cad.class_id AND cssc.studying_date = cad.dateinfo
GROUP BY SUBSTRING(ctudd.class_date, 1, 4), SUBSTRING(ctudd.class_date, 6, 2), ctudd.class_id;
-- 年维度
INSERT INTO TABLE itcast_dws.class_attendance_dws PARTITION(yearinfo, monthinfo, dayinfo)
SELECT
SUBSTRING(ctudd.class_date, 1, 4) AS dateinfo,
ctudd.class_id,
SUM(cssc.studying_student_count) AS studying_student_count,
SUM(cad.morning_att_count) AS morning_att_count,
CONCAT(CAST((morning_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_att_ratio,
SUM(cad.afternoon_att_count) AS afternoon_att_count,
CONCAT(CAST((afternoon_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_att_ratio,
SUM(cad.evening_att_count) AS evening_att_count,
CONCAT(CAST((evening_att_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_att_ratio,
SUM(cad.morning_late_count) AS morning_late_count,
CONCAT(CAST((morning_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_late_ratio,
SUM(cad.afternoon_late_count) AS afternoon_late_count,
CONCAT(CAST((afternoon_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_late_ratio,
SUM(cad.evening_late_count) AS evening_late_count,
CONCAT(CAST((evening_late_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_late_ratio,
SUM(cld.morning_leave_count) AS morning_leave_count,
CONCAT(CAST((morning_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_leave_ratio,
SUM(cld.afternoon_leave_count) AS afternoon_leave_count,
CONCAT(CAST((afternoon_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_leave_ratio,
SUM(cld.evening_leave_count) AS evening_leave_count,
CONCAT(CAST((evening_leave_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_leave_ratio,
SUM(ctd.morning_truant_count) AS morning_truant_count,
CONCAT(CAST((morning_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS morning_truant_ratio,
SUM(ctd.afternoon_truant_count) AS afternoon_truant_count,
CONCAT(CAST((afternoon_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS afternoon_truant_ratio,
SUM(ctd.evening_truant_count) AS evening_truant_count,
CONCAT(CAST((evening_truant_count / studying_student_count) * 100 AS DECIMAL(10,2)), '%') AS evening_truant_ratio,
'5' AS time_type,
SUBSTRING(ctudd.class_date, 1, 4) AS yearinfo,
'-1' AS monthinfo,
'-1' AS dayinfo
FROM (select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
LEFT JOIN itcast_dwm.class_attendance_dwm AS cad ON ctudd.class_id = cad.class_id AND ctudd.class_date = cad.dateinfo
LEFT JOIN itcast_dwm.class_leave_dwm AS cld
ON cad.dateinfo = cld.dateinfo AND cad.class_id = cld.class_id LEFT JOIN itcast_dwm.class_truant_dwm AS ctd
ON cad.dateinfo = ctd.dateinfo AND cad.class_id = ctd.class_id LEFT JOIN (select * from itcast_dimen.class_studying_student_count_dimen where studying_student_count is not null ) cssc
ON cssc.class_id = cad.class_id AND cssc.studying_date = cad.dateinfo
GROUP BY SUBSTRING(ctudd.class_date, 1, 4), ctudd.class_id;
思路:
DWM - 1: 以课表为主(确保有课的每一天都计算,因为学生打卡可能有某一天上课没有一个人打卡),关联应出勤人数表
关联条件:班级id和日期
然后关联DWM层的3个班级结果表(出勤、迟到,请假,旷课),关联条件:班级ID 和 日期
然后抽取字段组合即可
写出,作为日维度数据
然后和上面的5表关联条件一样,只不过加入按年和月分组(月维度),写出作为月维度数据
然后和在计算年维度即可
DWM - 2:
在DWS - 1的基础上,找出日维度的数据,俩字段相除 得到比率即可。
在DWS - 1的基础上,找出月维度的数据,俩字段相除 得到比率即可。
在DWS - 1的基础上,找出年维度的数据,俩字段相除 得到比率即可。
DWS -> MySQL
略
看板5增量操作
当做增量的操作,一定要先想:
- 历史数据状态的维护
- 检查硬性条件是否满足(需求的字段有没有)
- 怎么做,做哪种(SCD2)
看板5 2个事实表 3 个维度表:
维度表:全量抽取替换
事实表:
- 打卡表:没有历史状态,全生命周期就一个状态
打卡
,所以直接按照日期采集增量即可 - 请假表:有历史状态,比如这一条请假的经历的各种状态:
提交申请、审核通过、审核拒绝...
问题1
请假表,在业务系统的源数据中,没有UPDATE_TIME字段,我们无从判断状态的更新。
解决思路:
方式一:
每天一个表。
每个表都是全量抽取。
方式二:
暂时用不到,因为没学过
使用canal
等,可以监控MySQL Binlog
的框架,来监控业务数据库的数据变更。
流的形式,才是能够100%采集到历史状态
方式三:
社会工程学。
找后台开发人员 + 字段。
请假表采集脚本示例
-- 请假表
hive -e "
CREATE TABLE student_leave_apply_ods_${dayinfo} {
......
};
"
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from student_leave_apply
where SUBSTRING(begin_time, 1, 10) <= '昨天' AND SUBSTRING(end_time, 1, 10) >= '昨天'
AND $CONDITIONS' \
--hcatalog-database itcast_ods \
--hcatalog-table student_leave_apply_ods_${dayinfo} \
-m 6 \
--split-by id
DWM DWS 每日增量统计的流程
我们前面DWM和DWS的增量部分,每一个表的计算的SQL语句,都是以课表的上课日为主题线索来进行计算。
所有,我们只需要将前面的SQL中的:
(select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
改为:
(select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼' AND class_date = '昨天') as ctudd
加一个AND条件即可,这样整个SQL语句就按照昨天一天的数据来进行计算了。
问题2
假设按照每天一个表的场景来采集数据。
对于有的学生,请假2天或者更久。这个数据会被遗弃。
创建时间 请假开始 请假结束 3天假
2020-01-01 2020-01-01 2020-01-03
WHERE 请假开始 <= 采集日 AND 请假结束 >= 采集日 全部按照日来计算
采集日为1号: 2020-01-01 <= 2020-01-01 AND 2020-01-03 >= 2020-01-01
采集日为2号: 2020-01-01 <= 2020-01-02 AND 2020-01-03 >= 2020-01-02
采集日为3号: 2020-01-01 <= 2020-01-03 AND 2020-01-03 >= 2020-01-03
计算 WHERE class_date = 采集日
2020-01-02 号采集 1号数据,这一条数据被采集走。
存入到 1号的表中。
计算的时候,计算1号的请假数据,这个请假的时间范围包含1号,1号请假能计算出来。
3号采集2号的数据,这一条数据不会被采集。
2号的表,就没有这个请假记录,计算2号请假就会漏掉。
解决方法:
-- 在SQOOP的采集脚本中,WHERE条件替换为
where SUBSTRING(begin_time, 1, 10) <= '昨天' AND SUBSTRING(end_time, 1, 10) >= '昨天'
-- 不按照create_time 来采集,按照请假的区间来采集
项目回想
- 学习了数仓理论
- Hive的各种优化
- 完成了5个看板的操作
- 建模分析、需求分析
- 操作语句
- git工具的使用
- 帆软BI的安装和入门
两个事实表:
- 打卡表无需SCD
- 请假表需要SCD(SCD2最好),但是由于源数据中没有UPDATE_TIME相关的字段。所以只能:
- 要么抛出这个历史状态信息
- 要么按天分表存储
对于这个看板需求,我们只计算请假成功的学生信息。不计算历史的状态相关内容。
所以SCD2可以不做。
如果要做,只能按天分表了
,来监控业务数据库的数据变更。
流的形式,才是能够100%采集到历史状态
方式三:
社会工程学。
找后台开发人员 + 字段。
请假表采集脚本示例
-- 请假表
hive -e "
CREATE TABLE student_leave_apply_ods_${dayinfo} {
......
};
"
sqoop import \
--connect jdbc:mysql://192.168.52.150:3306/teach \
--username root \
--password 123456 \
--query 'select *, FROM_UNIXTIME(unix_timestamp(),"%Y-%m-%d") as dt from student_leave_apply
where SUBSTRING(begin_time, 1, 10) <= '昨天' AND SUBSTRING(end_time, 1, 10) >= '昨天'
AND $CONDITIONS' \
--hcatalog-database itcast_ods \
--hcatalog-table student_leave_apply_ods_${dayinfo} \
-m 6 \
--split-by id
DWM DWS 每日增量统计的流程
我们前面DWM和DWS的增量部分,每一个表的计算的SQL语句,都是以课表的上课日为主题线索来进行计算。
所有,我们只需要将前面的SQL中的:
(select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼') as ctudd
改为:
(select * from itcast_dimen.course_table_upload_detail_dimen where nvl(content,'') != '' and content !='开班典礼' AND class_date = '昨天') as ctudd
加一个AND条件即可,这样整个SQL语句就按照昨天一天的数据来进行计算了。
问题2
假设按照每天一个表的场景来采集数据。
对于有的学生,请假2天或者更久。这个数据会被遗弃。
创建时间 请假开始 请假结束 3天假
2020-01-01 2020-01-01 2020-01-03
WHERE 请假开始 <= 采集日 AND 请假结束 >= 采集日 全部按照日来计算
采集日为1号: 2020-01-01 <= 2020-01-01 AND 2020-01-03 >= 2020-01-01
采集日为2号: 2020-01-01 <= 2020-01-02 AND 2020-01-03 >= 2020-01-02
采集日为3号: 2020-01-01 <= 2020-01-03 AND 2020-01-03 >= 2020-01-03
计算 WHERE class_date = 采集日
2020-01-02 号采集 1号数据,这一条数据被采集走。
存入到 1号的表中。
计算的时候,计算1号的请假数据,这个请假的时间范围包含1号,1号请假能计算出来。
3号采集2号的数据,这一条数据不会被采集。
2号的表,就没有这个请假记录,计算2号请假就会漏掉。
解决方法:
-- 在SQOOP的采集脚本中,WHERE条件替换为
where SUBSTRING(begin_time, 1, 10) <= '昨天' AND SUBSTRING(end_time, 1, 10) >= '昨天'
-- 不按照create_time 来采集,按照请假的区间来采集
项目回想
- 学习了数仓理论
- Hive的各种优化
- 完成了5个看板的操作
- 建模分析、需求分析
- 操作语句
- git工具的使用
- 帆软BI的安装和入门
两个事实表:
- 打卡表无需SCD
- 请假表需要SCD(SCD2最好),但是由于源数据中没有UPDATE_TIME相关的字段。所以只能:
- 要么抛出这个历史状态信息
- 要么按天分表存储
对于这个看板需求,我们只计算请假成功的学生信息。不计算历史的状态相关内容。
所以SCD2可以不做。
如果要做,只能按天分表了
标签:count,comment,一个,最后,class,--,student,看板,id 来源: https://blog.csdn.net/xianyu120/article/details/118467831