一、数据仓库

1、概念

数据仓库(Data Warehouse)是一个面向主题的(Subject Oriented)、集成的(Integrated)、相对稳定的(Non-Volatile)、反映历史变化的(Time Variant)数据集 合,用于支持管理决策(Decision Making Support)和信息的全局共享(Global Sharing of Information)。

2、作用

通过对数据的分析,得出各种指标,为决策做服务参考。也可以做为公司的数据集中的一个地方。
日志数据、数据库数据、外来买的其他数据— 将这些数据统一纳入我们的数据仓库,进行一个汇总。

3、特点

  1. 数据仓库的数据是面向主题的
    2. 数据仓库的数据是集成的
    3. 数据仓库的数据是非易失的
    4. 数据仓库的数据是随时间不断变化的

    4、数据集市

    统计出来的指标

    5、数据仓库的架构

    1) inmon架构
    2) kimball架构
    两个架构,Kimball 我们使用的最多,因为它的层级少,比inmon少了一个数据集市的一层。因为比较的方便。
    kimball架构,需要的指标,当前需要,就立马统计,比较的方便。Inmon在项目一开始,就有明确的目标,专业性强,人员需要的也多,不太适合小公司使用。

    6、数据仓库的分层(重点)

    Java的开发:遵循三层架构 控制层、服务层、数据操作层
    数据仓库的开发,为了效率以及维护成本,我们也需要将数据进行分层。
    为什么要分层? 数据的来源很多,很杂,而我们展示给用的数据,需要很精简,很少。
    从原始数据到我们的展示的指标数据,中间需要大量的工作,我们将这些工作通过分层,进行规划。
    image.png
    数仓的常见分层一般为3层,分别为:数据操作层、数据仓库层和数据集市层 。
    ODS层:一般存储原始数据。
    DW层:进行数据的清洗以及汇总。
    DIM中存放的是公共的维度信息(客户类型、城市的类型)
    APP层:统计出来的指标数据。
    ODS层概念: Operate data store,操作数据存储,是最接近数据源中数据的一层,数据源中的数据,经过抽取、 洗净、传输,也就说传说中的ETL之后,装入本层。本层的数据,总体上大多是按照源头业务系统的分 类方式而分类的。 也可以在这一层不清洗,在dw层清洗也可以的。
    DW层: Data warehouse,数据仓库层。在这里,从ODS层中获得的数据按照主题建立各种数据模型。
    DW层可以分为三层,不是说必须使用,可以选择性的使用一层到三层,每一层都是对上一层的数据的聚合,所谓的聚合可以认为是将多个表合并为一个表。
    1. 数据明细层:DWD(Data Warehouse Detail)
    该层一般保持和ODS层一样的数据粒度,并且提供一定的数据质量保证。同时,为了提高数据明细层的易用性,该层会采用一些维度退化手法,将维度退化至事实表中,减少事实表和维表的关联。
    另外,在该层也会做一部分的数据聚合,将相同主题的数据汇集到一张表中,提高数据的可用性,后文会举例说明。
    2. 数据中间层:DWM(Data WareHouse Middle)
    该层会在DWD层的数据基础上,对数据做轻度的聚合操作,生成一系列的中间表,提升公共指标的复用性,减少重复加工。
    直观来讲,就是对通用的核心维度进行聚合操作,算出相应的统计指标。
    3. 数据服务层:DWS(Data WareHouse Servce)—最重要
    宽表—就是将之前多个表的字段汇总起来的表,表字段非常的多,称之为宽表。
    又称数据集市或宽表。按照业务划分,如流量、订单、用户等,生成字段比较多的宽表,用于提供后续的业务查询,OLAP分析,数据分发等。
    一般来讲,该层的数据表会相对比较少,一张表会涵盖比较多的业务内容,由于其字段较多,因此一般也会称该层的表为宽表。
    在实际计算中,如果直接从DWD或者ODS计算出宽表的统计指标,会存在计算量太大并且维度太少的问题,因此一般的做法是,在DWM层先计算出多个小的中间表,然后再拼接成一张DWS的宽表。由于宽和窄的界限不易界定,也可以去掉DWM这一层,只留DWS层,将所有的数据在放在DWS亦可。

    DM层:

    存放报表数据的,也就是我们常说的统计出来的结果。

    DIM层:维度层 —看待事务的角度

    同样的一批人: 性别的角度(性别表)
    国家的角度(国家表)
    学历的角度(学历表)

为什么要分这么多层?
1、数据更加清晰明了
2、将一个杂乱的数据,一层层整理出来,便于我们后面的统计,由于我们不知道以后的一个统计的方向,所以我们将数据根据粗细力度,变为一个个的表。
猪 —> 分层处理(清洗一部分,腌制一部分,放冰箱一部分,搞成半成品备用一部分) —> 红烧肉
数据分层的实质性的好处就是可以重复利用。
image.png

二、外卖项目

1、项目的了解

  1. 本平台是外卖行业类的自营业务的运营分析平台的一部分,通过对业务数据的分析,获得用户主题和订单的相关分析情况,生成用户的访问模型和用户的订单行为模型,对访问时段和下单区域分布进行分析和报表展示。<br />明确我们分析的主题是:用户和订单<br />干的事情:先创建用户的表以及订单相关的表,然后统计订单的下单时间以及分散的区域。<br />具体的目标就是:
l 学校下单总数,

l 公司下单总数,

l 家里下单总数,

l 凌晨下单总数,

l 上午下单总数,

l 中午下单总数,

l 下午下单总数,

l 晚上下单总数

时间口径:

凌晨:0-05  上午:06-12 中午:13-15 下午16-20 晚上:21-24

2、数据来源

来自于mysql数据库。
执行sql语句: data_init_mt.sql
数据的导入:
image.png
image.png
image.png
image.png
开始产生数据。不要动。
us_order 表中,看到有数据产生,就可以停止了
image.png

3、分析一下表结构

分析的指标是:
2022-01-06单天,整个外卖平台,在家里,在学校,在公司的一个下单数量分别是多少,看看哪个更多?
需要us_order 表,但是us_order 表看不出是哪里下的单,只能看到下单用户,user、user_addr表中,有一个字段
user_order_flag,可以根据这个字段,来统计是在哪里下的单。
综上所述需要的表有:
us_order 、user 、user_addr 表三个表进行关联。
2022-01-06单天,整个外卖平台,凌晨、早上、中午、下午、晚上各个时间段的一个下单数量哪个更多。
可以根据us_order 表中的下单时间,推测是凌晨、早上、中午、下午、晚上。

4、思路梳理

将数仓项目分为:Hive
1、不断的将mysql 中的数据,拉取到 ods层的表中。
ODS层:存储订单数据以及订单的配送数据(跟数据库中的数据保持一致) us_order order_delivery
2、dim 维度表
这个里面存储用户信息(user) 以及 用户的配送地址 (user_address)
3、dw层—数据仓库层
制作一个关于订单的宽表(字段非常的多) 将以上四个表中的重要的字段全部合并为dw层中的一个表,数据就是以上四个表的
关联之后的数据。
4、dm层 —- 分析出来的指标
就根据之前描述的业务,编写SQL语句,查询第三步的宽表数据,将查询的结果保存在 dm层中的表里面。
5、将dm层的统计的结果数据导出到mysql数据库中即可。
总体来讲:先将mysql中的四个表的数据导入到hive表中,四个表数据合并为一个表A,查询表A的数据做出统计,将统计的结果放入到B中,然后将B中结果导出到mysql对应的表里面即可。

5、开始进行编程

第一步:先创建数据库以及建表(4个)

先创建各个层级的数据库:

create database if not exists ods_el;  #ods层数据库:存放订单数据
create database if not exists dw_el;   #数据仓库数据库,存放宽表
create database if not exists dm_el;   #存放统计的结果
create database if not exists dim_el;  #存放维度信息(user,user_addr)

先根据数据库表的字段,创建一个一模一样的hive表 us_order

create table if not exists ods_el.us_order(
  `order_id` bigint,
  `order_no` string,
  `order_date` string,
  `user_id` bigint,
  `user_name` string,
  `order_money` double,
  `order_type` int,
  `order_status` int,
  `pay_status` int,
  `pay_type` int,
  `order_source` string,
  `update_time` string
)
partitioned by(dt string)
row format delimited 
fields terminated by ','
lines terminated by '\n'
stored as textfile;

创建order_delivery 表

CREATE TABLE if not exists ods_el.order_delivery (
  `order_id` bigint,
  `order_no` string,
  `consignee` string,
  `area_id` bigint,
  `area_name` string,
  `address` string,
  `mobile` bigint,
  `phone` string,
  `coupon_id` bigint,
  `coupon_money` double,
  `carriage_money` double,
  `create_time` string,
  `update_time` string,
  `addr_id` bigint
)
partitioned by(dt string)
row format delimited 
fields terminated by ','
lines terminated by '\n'
stored as textfile;

在维度层(dim_el)创建 user 以及user_addr

CREATE TABLE if not exists dim_el.`user` (
  `user_id` bigint,
  `user_name` string,
  `user_gender` tinyint,
  `user_birthday` string,
  `user_age` int,
  `constellation` string,
  `province` string,
  `city` string,
  `city_level` tinyint,
  `e_mail` string,
  `op_mail` string,
  `mobile` bigint,
  `num_seg_mobile` int,
  `op_Mobile` string,
  `register_time` string,
  `login_ip` string,
  `login_source` string,
  `request_user` string,
  `total_score` double,
  `used_score` double,
  `is_blacklist` tinyint,
  `is_married` tinyint,
  `education` string,
  `monthly_income` double,
  `profession` string,
  `create_date` string 
)row format delimited 
fields terminated by ','
lines terminated by '\n'
stored as textfile;

一定要注意,user表这个名字非常的特殊,需要使用 包括起来,否则报错。

CREATE TABLE if not exists dim_el.user_addr (
  `user_id` bigint,
  `order_addr` string,
  `user_order_flag` tinyint,
  `addr_id` bigint,
  `arear_id` int
)
row format delimited 
fields terminated by ','
lines terminated by '\n'
stored as textfile;

第二步:从MySQL 中导入数据到Hive 的表中。(Sqoop脚本)

1)导入user数据

sqoop import \
--connect jdbc:mysql://wlw01:3306/qf_el \
--username root \
--password root \
--query "SELECT * FROM qf_el.user where 1=1 and \$CONDITIONS" \
--fields-terminated-by ',' \
--target-dir /user/hive/warehouse/dim_el.db/user \
--delete-target-dir \
--m 1

查询数据: select * from user; 只有user表需要加两个点括起来。
2、导入user_addr数据

sqoop import \
--connect jdbc:mysql://wlw01:3306/qf_el \
--username root \
--password root \
--query "SELECT * FROM qf_el.user_addr where 1=1 and \$CONDITIONS" \
--fields-terminated-by ',' \
--target-dir /user/hive/warehouse/dim_el.db/user_addr \
--delete-target-dir \
--m 1

查询数据是否导入成功。 select * from user_addr;

3、导入us_order 表

sqoop import \
--connect jdbc:mysql://wlw01:3306/qf_el \
--username root \
--password root \
--query "SELECT * FROM qf_el.us_order uo WHERE DATE(uo.order_date) = '2022-01-06' and \$CONDITIONS" \
--fields-terminated-by ',' \
--target-dir /user/hive/warehouse/ods_el.db/us_order/dt=2022-01-06 \
--delete-target-dir \
--m 1

导入成功后,发现没有数据。 select * from us_order
需要手动的添加一个分区:

alter table ods_el.us_order add partition(dt='2022-01-06');

再次查询,发现有数据了。

4、导入order_deliver表
发现没有1月6日的数据,修改我们的源数据

update order_delivery set create_time='2022-01-06 11:38:28';
sqoop import \
--connect jdbc:mysql://wlw01:3306/qf_el \
--username root \
--password root \
--query "SELECT * FROM qf_el.order_delivery od WHERE DATE(od.create_time) = '2022-01-06' and \$CONDITIONS" \
--fields-terminated-by ',' \
--target-dir /user/hive/warehouse/ods_el.db/order_delivery/dt=2022-01-06 \
--delete-target-dir \
--m 1 \
--hive-import \
--hive-database ods_el \
--hive-table order_delivery \
--hive-partition-key dt \
--hive-partition-value 2022-01-06

查看数据,是否导入成功。
select * from order_delivery;
至此:我们将mysql中的需要的数据,全部导入到我们的hive的ods层,ods层是原生数据层,一般不做任何的处理,将数据全部搬过来。

第三步:数据仓库dw层

根据以上四个表,创建一个dw层的一个关于order主题的宽表。(order_wide_detail)
1、创建表

create external table if not exists dw_el.order_wide_detail(
  `order_id` bigint,
  `order_no` string,
  `order_date` string,
  `user_id` bigint,
  `user_name` string,
  `order_money` double,
  `order_type` int,
  `order_status` int,
  `pay_status` int,
  `pay_type` int,
  `order_source` string,
  `update_time` string,
   user_order_flag string,
   order_hour string,
  `user_gender` tinyint,
  `user_birthday` string,
  `user_age` int,
  `constellation` string,
  `province` string,
  `city` string,
  `city_level` tinyint
)
partitioned by(dt string)
row format delimited 
fields terminated by ','
lines terminated by '\n'
stored as textfile;

2、表中插入数据

insert into dw_el.order_wide_detail partition(dt='2022-01-06')
select 
uo.order_id,
uo.order_no,
uo.order_date,
uo.user_id,
uo.user_name,
uo.order_money,
uo.order_type,
uo.order_status,
uo.pay_status,
uo.pay_type,
uo.order_source,
uo.update_time,
ua.user_order_flag user_order_flag,
hour(uo.order_date) order_hour,
uu.user_gender,
uu.user_birthday,
uu.user_age,
uu.constellation,
uu.province,
uu.city,
uu.city_level
from 
ods_el.us_order uo
left join ods_el.order_delivery od
on uo.order_id = od.order_id
left join dim_el.user_addr ua
on od.addr_id = ua.addr_id
left join dim_el.`user` uu
on uo.user_id=uu.user_id
where uo.dt='2022-01-06';

至此我们的宽表已经创建,并且数据已经添加,这个表就是我们很多指标统计的依据。

第四步:DM层—关注指标统计

1、统计在2022-01-06当天,在学校,在公司,在家里的下单量,以及在凌晨,上午,中午,下午,晚上的一个下单量。
创建表

create external table if not exists dm_el.order_hour_locat_dist(
`date` string,
school_orders int,
company_orders int,
home_orders int,
orders_0_5 int,
orders_6_12 int,
orders_13_15 int,
orders_16_20 int,
orders_21_24 int
)
partitioned by(dt string)
row format delimited fields terminated by ','
lines terminated by '\n'
stored as textfile
;

开始统计,并且把统计结果存放到我们的order_hour_locat_dist。
通过查询我上面dw层宽表信息,拿到我想要的指标信息。
image.png
查询并且插入结果dm层:

insert into dm_el.order_hour_locat_dist partition(dt='2022-01-06')
select 
to_date(od.order_date) dt,
sum(case when od.user_order_flag=1 then 1 else 0 end) school_orders,
sum(case when od.user_order_flag=2 then 1 else 0 end) company_orders,
sum(case when od.user_order_flag=3 then 1 else 0 end) home_orders,
sum(case when od.order_hour between 0 and 5 then 1 else 0 end) order_0_5,
sum(case when od.order_hour between 6 and 12 then 1 else 0 end) order_6_12,
sum(case when od.order_hour between 13 and 15 then 1 else 0 end) order_13_15,
sum(case when od.order_hour between 16 and 20 then 1 else 0 end) order_16_20,
sum(case when od.order_hour between 21 and 23 then 1 else 0 end) order_21_24
from 
dw_el.order_wide_detail od
where to_date(od.order_date) = '2022-01-06'
group by to_date(od.order_date);

select * from dm_el.order_hour_locat_dist;

第五步:将dm层统计的结果导出到mysql中

在mysql中创建一个存储结果的表。
image.png
通过sqoop,将结果导出到mysql的表中。

sqoop export \
--connect jdbc:mysql://wlw01:3306/el_report \
--username root \
--password root \
--table order_locat_hour_dist \
--fields-terminated-by ',' \
--export-dir /user/hive/warehouse/dm_el.db/order_hour_locat_dist/dt=2022-01-06/ \
--m 1

查看el_report数据库中的order_locat_hour_dist是否有数据。
—————————————以上就是一个指标的整体流程,下面是两个新的指标————————————
下一个指标:每个年龄段,它的一个整体消费情况
年段口径:0-16,17-25,26-35,36-60,61-80,80++
创建一个表,用于接收统计的结果:

create external table if not exists dm_el.order_paytype_age_dist(
`date` string,
pay_type string,  
orders_price_0_16 double,
orders_price_17_25 double,
orders_price_26_35 double,
orders_price_36_60 double,
orders_price_61_80 double,
orders_price_81_pluss double
)
partitioned by(dt string)
row format delimited fields terminated by ','
lines terminated by '\n'
stored as textfile
;

开始编写统计的sql语句,插入到上面的表中:

insert into dm_el.order_paytype_age_dist partition(dt="2022-01-06")
select
to_date(od.order_date) dt,
od.pay_type pay_type,
round(sum(case when od.user_age between 0 and 16 then od.order_money else 0 end),3) orders_price_0_16,
round(sum(case when od.user_age between 17 and 25 then od.order_money else 0 end),3) orders_price_17_25,
round(sum(case when od.user_age between 26 and 35 then od.order_money else 0 end),3) orders_price_26_35,
round(sum(case when od.user_age between 36 and 60 then od.order_money else 0 end),3) orders_price_36_60,
round(sum(case when od.user_age between 61 and 80 then od.order_money else 0 end),3) orders_price_61_80,
round(sum(case when od.user_age >= 81 then od.order_money else 0 end),3) orders_price_81_pluss
from dw_el.order_wide_detail od
where to_date(od.order_date) = '2022-01-06'
group by to_date(od.order_date),od.pay_type
;

统计完,将hive表中的结果导出到mysql
mysql先建一个表:

CREATE TABLE `el_report`.`order_paytype_age_dist` (
  `dt` varchar(10) DEFAULT NULL,
  `pay_type` varchar(10) DEFAULT NULL,
  `orders_price_0_16` double(10,3) DEFAULT '0.000',
  `orders_price_17_25` double(10,3) DEFAULT '0.000',
  `orders_price_26_35` double(10,3) DEFAULT '0.000',
  `orders_price_36_60` double(10,3) DEFAULT '0.000',
  `orders_price_61_80` double(10,3) DEFAULT '0.000',
  `orders_price_81_pluss` double(10,3) DEFAULT '0.000'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

导出数据到mysql:

sqoop export \
--connect jdbc:mysql://wlw01:3306/el_report \
--username root \
--password root \
--table order_paytype_age_dist \
--fields-terminated-by ',' \
--export-dir /user/hive/warehouse/dm_el.db/order_paytype_age_dist/dt=2022-01-06/ \
--m 1

最后一个指标:在2022-01-06那天,全国各个省份的一个下单情况。
创建一个hive表,用于接收统计的结果

create external table if not exists dm_el.order_map_dist(
`date` string,
 province string,
 orders_cnt int,
 order_prices double
)
partitioned by(dt string)
row format delimited fields terminated by ','
lines terminated by '\n'
stored as textfile
;

统计并将结果存放到hive表中

insert into dm_el.order_map_dist partition(dt="2022-01-06")
select
to_date(od.order_date) dt,
od.province province,
count(od.order_id) order_cnt,
round(sum(od.order_money),3) order_prices
from dw_el.order_wide_detail od
where to_date(od.order_date) = '2022-01-06'
group by to_date(od.order_date),od.province
;

然后创建mysql

CREATE TABLE `el_report`.`order_map_dist` (
  `dt` varchar(10) DEFAULT NULL,
  `province` varchar(255) DEFAULT NULL,
  `order_cnt` int DEFAULT '0',
  `order_prices` double(10,3) DEFAULT '0.000'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

导出数据到mysql,注意字符编码

sqoop export \
--connect "jdbc:mysql://wlw01:3306/el_report?useUnicode=true&characterEncoding=UTF-8" \
--table order_map_dist \
--username root \
--password root \
--fields-terminated-by ',' \
--export-dir /user/hive/warehouse/dm_el.db/order_map_dist/dt=2022-01-06/ \
--m 1