其他分享
首页 > 其他分享> > 看板5最后一个看板

看板5最后一个看板

作者:互联网

看板4回顾

  1. 在做分析的时候,所有的表都要考虑到增量的情况

    1. 维度

      1. 数据量少,直接覆盖即可
      2. 数据量大,根据时间来抽取最新的即可

      有的表,本质上是实时表,但是可能做维度的作用。

      比如看板4 的 itcast_clazz表,是学生的报名信息表。

      本质上是一个学生报名某个校区某个学科产生的事实时间,本质上是事实表的属性

      但是在看板4中,是维度的作用。

      对于这个表,放入DIMEN层,或者ODS层都可以。

      做增量的时候,对于任何有事实属性的表,都要考虑到它的增量采集问题,也就是

      1. 选择SCD的模式,比如选择SCD2(保留全部的历史状态)
      2. 选项SCD的实现模式,比如SCD2
        1. 通过增加表来实现(每一次采集都产生一个新表)
        2. 通过增加列来实现(在表中增加对行数据的有效期做判断)(比如valid_start_from和valid_end_from)
    2. 事实

      考虑到它的增量采集问题,也就是

      1. 选择SCD的模式,比如选择SCD2(保留全部的历史状态)
      2. 选项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(阶段)

阶段之间都会有依赖关系:

如果多个State之间没有依赖,他们如果能够并行执行就能够提高集群的整体资源利用率。

参数:

set hive.exec.parallel=true,可以开启并发执行,默认为false。
set hive.exec.parallel.thread.number=16; //同一个sql允许的最大并行度,默认为8。

小文件优化

建议在企业中去使用,个人虚拟机电脑的性能不足,会导致性能下降。

前置条件:MapReduce的执行结果,不是一个单独的文件。而是多个文件。

如果MR任务的结果产生了很多的小文件存储在HDFS中,那么就会造成性能的下降(对NameNode的压力会很大)

小文件的HDFS影响

举例:

磁盘占用率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

矢量化查询

对于分布式系统的优化,有两个方向:

  1. 增加分布式能力(增加并行计算能力)(横向拓展)

    说白了就是加机器,加CPU数量

  2. 增加单机的处理能力(纵向拓展)

矢量化查询,就是类型2的优化。

MapReduce默认情况下,对数据的处理是一条条的处理的。

一条条处理是很正常的处理方式,写到这里,没有任何歧义,只是为了烘托矢量化查询而已。

矢量化查询是指,对数据一批批的处理。

要执行矢量化是有要求的:

参数

# 开启矢量化查询
set hive.vectorized.execution.enabled=true;
# 开启矢量化在reduce端
set hive.vectorized.execution.reduce.enabled = true;

这种带有enable类型的,只是指开启某个功能,功能能不能用得上,是hive的判断。

比如前面将的map join

读取零拷贝优化

概念:尽量减少在读取的时候读取的数据量的大小。

条件:

一般情况下,我们写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分组统计的倾斜处理

对于数据倾斜,典型的两个性能点:

分组聚合。

前提条件:对数据走平均打散,不按照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的计算模型,就2个:

严格意义来说,MR叫做可供使用的算子就2个,一个是map算子一个是reduce算子

很多的业务计算都受限map和reduce方法的限制,因为,比如map方法

map(传入参数固定){

​ 我们只能在这个部分,做自由处理。

​ return 返回形式固定

}

reduce(传入参数固定){

​ 我们只能在这个部分,做自由处理。

​ return 返回形式固定

}

MR的迭代

基于前面的概念,所以很多的业务计算本质上是迭代的计算。

image-20210111115155248

如图,某些复杂的业务场景可能会如图所示执行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班级在读人数表来确定,某一天某个班应该有多少人出勤。

其余的维度和指标 和 需求一一致。

需求分析总结

后续的需求,经过细看,发现,就是要计算:

维度:

学生维度要求能够被下钻

也就是对于学生来说,每个学生每一天的出勤状态都应该被统计到。

计算注意点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PToKpJcP-1625405131309)(https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/image-20210112091958099.png)]

看板5建模

ODS

表有2个:

ods作为原始存储,原样不动采集来就好

学生打卡记录

表存储:ORC

压缩:Zlib、Snappy

分区:日期

请假申请表

存储:ORC

压缩:Zlib、Snappy

分区:日期

增量SCD2?

DIMEN

DWD : 略

DWM

DWM - 1

所以在预聚合阶段,我们要对整个需求的最小维度做聚合

可以得知,在DWM第一步我们要得到:

能够反映:某日 某学生,上午、下午、晚上的出勤状态表

DWM - 2

在上面的基础上,我们得到了学生和日 维度的聚合结果

还需要向上聚合,得到班级和日的聚合结果

可以得到的表能够反映:

某日,某班级,上午、下午、晚上的出勤人数

某日,某班级,上午、下午、晚上的迟到人数

某日,某班级,上午、下午、晚上的请假人数

某日,某班级,上午、下午、晚上的旷课人数

如上,对班级做聚合得到4个表,每个表都记录了某日 某班级 上午、下午、晚上在4个指标上的量化结果。

经过DWM的两个阶段操作,我们最终得到了:

某日、某班级在上午、下午、晚上的4个方向的指标量化记录

DWS

DWS - 1

在DWM中已经得到

的聚合结果

在DWS在这个基础上,做月聚合,和年聚合即可

这样DWS最终可以得到

某年 某班级 上午、下午、晚上 在4个指标方向上的记录

某年某月 某班级 上午、下午、晚上 在4个指标方向上的记录

某年某月某日 某班级 上午、下午、晚上 在4个指标方向上的记录

日期班级id上午出勤人数下午出勤人数晚上出勤人数上午请假人数下午请假人数晚上请假人数上午迟到人数下午迟到人数晚上迟到人数上午旷课人数下午旷课人数晚上旷课人数应出勤人数时间类型
2020-1-1NA1200020012003200020012003200020012003200020012003120001 年 2 月 3 日
202011-1NA11501601551501601551501601551501601553000
20201111579579579579100

看板5建模分析总结

整个建模的核心流程是:

  1. 从小到大
  2. 从各个维度的最小粒度开始做预聚合(DWM-1)(学生级别的维度和日级别的维度)
  3. 慢慢的向上提升维度(DWM-2,从学生提升到了班级级别)(日期级别还是日级别)
  4. 继续向上,从日提升到月,从月提升到年(DWS - 1)
  5. 继续计算,得到需要的各种(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 学生级别 + 日级别的聚合表,需要用到:

  1. 打卡信息表,记录学生的打卡时间(计算正常出勤和迟到)

  2. 作息时间表,用来确定学生的上下课时间

    关联条件:打卡表.time_table_id = 作息表.id

  3. 课表,用来确定上课日

    关联条件:打卡表.class_id = 课表.class_id AND 打卡表.signin_date = 课表.class_date

    过滤条件:对于课表如何确定哪天是有课,条件是:

    NVL(content, '') != '' AND content != '开班典礼'

  4. 聚合分组条件:

    1. 学生级别:按照班级+学生id分组
    2. 日级别:按照签到日期分组
    3. 最终: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;

班级请假人数统计

用到的表:

-- 插入一条示例数据,因为源数据中没人请假呢
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;

旷课人数统计

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增量操作

当做增量的操作,一定要先想:

看板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 来采集,按照请假的区间来采集

项目回想

  1. 学习了数仓理论
  2. Hive的各种优化
  3. 完成了5个看板的操作
    1. 建模分析、需求分析
    2. 操作语句
  4. git工具的使用
  5. 帆软BI的安装和入门

两个事实表:

对于这个看板需求,我们只计算请假成功的学生信息。不计算历史的状态相关内容。

所以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 来采集,按照请假的区间来采集

项目回想

  1. 学习了数仓理论
  2. Hive的各种优化
  3. 完成了5个看板的操作
    1. 建模分析、需求分析
    2. 操作语句
  4. git工具的使用
  5. 帆软BI的安装和入门

两个事实表:

对于这个看板需求,我们只计算请假成功的学生信息。不计算历史的状态相关内容。

所以SCD2可以不做。

如果要做,只能按天分表了

标签:count,comment,一个,最后,class,--,student,看板,id
来源: https://blog.csdn.net/xianyu120/article/details/118467831