暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从0到1掌握Hive SQL

王木工 2021-07-26
1059
文章目录:
1 前言
2 初识Apache Hive
    2.1 初识Hadoop
        2.1.1 Hadoop的核心架构
        2.1.2 Hadoop的生态圈
    2.2 初识Hive
        2.2.1 Hive简介
        2.2.2 Hive的架构
        2.2.3 Hive的数据存储结构
        2.2.4 Hive SQL
        2.2.5 其他
3 Hive SQL
    3.1 DQL语法
        3.1.1 数据检索
        3.1.2 检索数据处理
        3.1.3 数据过滤
        3.1.4 多表查询
        3.1.5 执行顺序
    3.2 Hive SQL函数
        3.2.1 日期函数
        3.2.2 字符串函数
        3.2.3 条件函数
        3.2.4 类型转换函数
        3.2.5 聚合函数
        3.2.6 窗口函数
    3.3 Hive SQL应用案例
        3.3.1 用户过去7天订单实付金额总和
        3.3.2 用户首次支付订单的距今天数
        3.3.3 支付订单金额高于300元的订单中,哪个商品一级分类最受客户自己偏爱
        3.3.4 过去90天内,登录App次数分布
        3.3.5 昨日做过App激活,并且是首次访问
        3.3.6 昨日做过App启动,同时满足前天做过App启动
        3.3.7 昨日之前做过App启动,但昨日没有做过启动App
        3.3.8 昨日做过App启动(且不是首次),同时满足前天没做过App启动
4 Hive SQL性能优化
    4.1 设计合适的数据模型
    4.2 优化Hive SQL查询语句
        4.2.1 列裁剪和分区裁剪
        4.2.2 谓词下推
        4.2.3 sort by 代替 order by
        4.2.4 group by 代替 distinct
        4.2.5 join优化
    4.3 合理进行Hive配置、MapReduce分配
    4.4 预计算与缓存计算结果
        4.4.1 定时预计算
        4.4.2 在中间表完成数据的初步聚合
        4.4.3 其他
5 总结 

1 前言

作为客户运营数据平台、数据项目的产品经理,日常工作中遇到过以下SQL的应用场景:
  1. 摸底数据源、数据质量

  2. 撰写指标、标签口径文档

  3. 讨论、撰写数据集成方案

  4. 与开发沟通确认开发逻辑

  5. 指标、标签的验收测试

  6. 撰写数据更新、脏数据处理方案等

通过以上列举的场景,可以发现数据产品经理对SQL的应用贯穿了项目的始终,对数据产品经理而言,掌握SQL这项技能非常重要;另外,由于个人非计算机相关专业科班出身,在工作中遇到了一些瓶颈,老板推荐了《Hive实战》一书。基于以上两点原因,近期进行了有关Hive、Hive SQL的专题学习,学习总结的内容尽量贴合数据产品经理的工作应用,主要会从下面的几个方面展开:
  1. 初识Hive:简单了解Hadoop、Hive的原理、特点、应用。(实际工作中产品经理一般不会直接参与Hive的安装配置、数据装载等,但有关大数据存储、计算话题讨论,需要基于对Hadoop、Hive的基础认知)

  2. Hive SQL:系统总结有关Hive SQL的DQL语法、函数、应用案例。(DQL语法和函数是SQL查询中高频应用的内容,同时也会结合数据化运营场景中的指标、标签的案例进行练习)

  3. Hive SQL优化:简单了解有关Hive SQL查询优化的内容。(当数据量大的时候,查询性能问题是必须考虑的内容)

下面进入正文部分: 

2 初识Apache Hive

Hive是基于Hadoop的一个数据仓库工具,为了更好地了解Hive,正式开始Hive的介绍之前先简单了解下Hadoop。

2.1 初识Hadoop

Hadoop是一个由Apache基金会所开发的分布式系统基础架构,主要解决海量数据的存储、分析计算问题。从广义上来说,Hadoop通常是指一个更广泛的概念——Hadoop的生态圈。接下来我们分别从Hadoop的核心架构、Hadoop的生态圈两个角度简单介绍Hadoop:

2.1.1 Hadoop的核心架构

Hadoop的核心是HDFS和MapReduce,HDFS为海量数据提供了存储,而MapReduce为海量数据提供了计算框架。
接下来看看这两个组件分别是如何工作的:
1、HDFS为海量数据提供了存储
整个HDFS有三个重要角色:NameNode(名称节点)、DataNode(数据节点)和Client(客户端)
(1)NameNode:是Master节点(主节点),可以看作是分布式文件系统中的管理者,主要负责管理文件系统的命名空间、集群配置信息和存储块的复制等。NameNode会将文件系统的Meta-data存储在内存中,这些信息主要包括了文件信息、每一个文件对应的文件块的信息和每一个文件块在DataNode的信息等。
(2)DataNode:是Slave节点(从节点),是文件存储的基本单元,它将Block存储在本地文件系统中,保存了Block的Meta-data,同时周期性地将所有存在的Block信息发送给NameNode。
(3)Client:切分文件;访问HDFS;与NameNode交互,获得文件位置信息;与DataNode交互,读取和写入数据。
(4)还有一个Block(块)的概念:Block是HDFS中的基本读写单元;HDFS中的文件都是被切割为block(块)进行存储的;这些块被复制到多个DataNode中;块的大小(通常为64MB)和复制的块数量在创建文件时由Client决定。
我们来简单看看HDFS的读写流程: 
写入流程:
  1. 用户向Client(客户机)提出请求。例如,需要写入200MB的数据。

  2. Client制定计划:将数据按照64MB为块,进行切割;所有的块都保存三份。

  3. Client将大文件切分成块(block)。

  4. 针对第一个块,Client告诉NameNode(主控节点),请帮助我,将64MB的块复制三份。

  5. NameNode告诉Client三个DataNode(数据节点)的地址,并且将它们根据到Client的距离,进行了排序。

  6. Client把数据和清单发给第一个DataNode。

  7. 第一个DataNode将数据复制给第二个DataNode。

  8. 第二个DataNode将数据复制给第三个DataNode。

  9. 如果某一个块的所有数据都已写入,就会向NameNode反馈已完成。

  10. 对第二个Block,也进行相同的操作。

  11. 所有Block都完成后,关闭文件。NameNode会将数据持久化到磁盘上。

读取流程:
  1. 用户向Client提出读取请求。

  2. Client向NameNode请求这个文件的所有信息。

  3. NameNode将给Client这个文件的块列表,以及存储各个块的数据节点清单(按照和客户端的距离排序)。

  4. Client从距离最近的数据节点下载所需的块。

2、MapReduce为海量数据提供了计算框架
MapReduce其实是一种编程模型。这个模型的核心步骤主要分两部分:Map(映射)和Reduce(归约)。 
当你向MapReduce框架提交一个计算作业时,它会首先把计算作业拆分成若干个Map任务,然后分配到不同的节点上去执行,每一个Map任务处理输入数据中的一部分,当Map任务完成后,它会生成一些中间文件,这些中间文件将会作为Reduce任务的输入数据。Reduce任务的主要目标就是把前面若干个Map的输出汇总到一起并输出。
为了更好理解,再看个例子:一个老师有100份试卷要阅卷。他找来5个帮手,扔给每个帮手20份试卷。帮手各自阅卷。最后,帮手们将成绩汇总给老师。

2.1.2 Hadoop的生态圈

经过时间的累积,Hadoop已经从最开始的两三个组件,发展成一个拥有20多个部件的生态系统。在整个Hadoop架构中,计算框架起到承上启下的作用,一方面可以操作HDFS中的数据,另一方面可以被封装,提供Hive、Spark SQL这样的上层组件的调用。 
为了更好地理解接下来有关Hive内容,在本模块简单介绍Hadoop的核心架构(HDFS和MapReduce)和生态圈。
关于Hadoop,想进一步学习的话推荐下面的资源:
  1. 知乎搜索:深入浅出大数据:到底什么是Hadoop?

  2. 阿里云大数据学习路线→Hadoop快速入门→第一章:Hadoop介绍

2.2 初识Hive

2.2.1 Hive简介

1、什么是Hive
Hive 依赖于 HDFS 存储数据,Hive 将 HQL 转换成 MapReduce 执行,所以说 Hive 是基于 Hadoop 的一个数据仓库工具,实质就是一款基于 HDFS 的 MapReduce 计算框架,对存储在 HDFS 中的数据进行分析和管理。 
2、为什么使用Hive
(1)直接使用 MapReduce 所面临的问题:
  •   人员学习成本太高

  •   MapReduce实现复杂查询逻辑开发难度太大

(2)为什么使用 Hive:
  •   更友好的接口:操作接口采用类 SQL 的语法,提供快速开发的能力

  •   更低的学习成本:避免了写 MapReduce,减少开发人员的学习成本

2.2.2 Hive的架构

从下图可以看出Hive架构图可以分为以下3个部分:
1、用户接口:
(1)CLI,Shell 终端命令行(Command Line Interface),采用交互形式使用 Hive 命令行与 Hive 进行交互,最常用(学习,调试,生产)
(2)JDBC/ODBC,是 Hive 的基于 JDBC 操作提供的客户端,用户(开发员,运维人员)通过 这连接至 Hive server 服务
(3)Web UI,通过浏览器访问 Hive 
2、元数据存储:
Hive 中的元数据通常包括:表的名字,表的列和分区及其属性,表的属性(内部表和 外部表),表的数据所在目录
Metastore 默认存在自带的 Derby 数据库中。缺点就是不适合多用户操作,并且数据存 储目录不固定。数据库跟着 Hive 走,极度不方便管理。解决方案通常是存我们自己创建的 MySQL 库(本地 或 远程),Hive 和 MySQL 之间通过 MetaStore 服务交互。
3、底层Driver:
Hive 的核心是驱动引擎, 驱动引擎由驱动器、编译器、优化器、执行器四部分组成:
(1)解释器:解释器的作用是将 HiveSQL 语句转换为抽象语法树(AST)
(2)编译器:编译器是将语法树编译为逻辑执行计划
(3)优化器:优化器是对逻辑执行计划进行优化
(4)执行器:执行器是调用底层的运行框架执行逻辑执行计划
为了好理解,图中对底层Driver模块进行了简单处理。 
结合Hive架构,可以把执行流程简化地描述为:HQL 通过命令行或者客户端被提交,经过语法解析器,运用元数据进行类型检测和语法分析,然后经过MR程序模板库把SQL转换成MR程序,接下来通过MR程序提交器把MR程序提交给YARN,然后YARN来安排执行。 

2.2.3 Hive的数据存储结构 

按数据的照粒度顺序,数据存储单元数据被组织为:
  • 数据库 Databases

  • 数据表 Tables

  • 分区 Partitions

  • 分桶 Buckets

1、数据库 Databases
在 HDFS 中表现为${hive.metastore.warehouse.dir}目录下一个文件夹。
2、数据表 Tables
在 HDFS 中表现所属 database 目录下一个文件夹。 
3、分区 Partitions
在 HDFS 中表现为 table 目录下的子目录。在Hive Select查询中,一般会扫描整个表内容,即便加了where条件,这样会消耗很多时间做没必要的工作。有时候只需要扫描表中关心的一部分数据,因此建表时引入了partition分区的概念,在where条件中指定在某个分区表中查询,就只扫描指定分区内的数据了。
4、分桶 Buckets
在 HDFS 中表现为同一个表目录或者分区目录下根据某个字段的值进行 hash 散列之后的多个文件。一般对join列进行分桶,通过减少查询量来提升查询效率。

2.2.4 Hive SQL

经过上面内容的介绍,相信已经明白Hive SQL在hive中扮演的角色,尤其是对于主要用SQL进行查询的同学,主要通过SQL与hive进行交互,故下面介绍下Hive SQL。 
1、Hive SQL语法分类
按着HQL语言的应用场景,可以分为如下三类:
  • DDL(Data Defination Language):数据库定义语言

  • DML(Data manipulation language):数据操作语言

  • DQL(data query language):数据查询语言

(1)DDL(Data Defination Language)
数据库操作语言主要包括:SHOW DATABASES显示数据库、SHOW TABLES显示表、CREATE TABLE创建表、ALTER TABLE更新表名等。
(2)DML(Data manipulation language)
数据操作语言主要是数据库增删改三种操作,DML包括:INSERT插入、UPDATE更新、DELETE删除。
(3)DQL(data query language)
数据查询语言包括:数据检索、检索数据处理、数据过滤、多表查询、执行顺序等内容,因为个人在工作中主要应用DQL和SQL函数,所以会在接下来的篇章中会重点学习总结DQL和函数相关内容。
2、Hive SQL和SQL的区别
Hive SQL 与 SQL 基本上一样,因为当初的设计目的,就是让会 SQL 不会编程MapReduce 的也能使用 Hadoop 进行处理数据。用法上的区别:
(1)HQL 不支持行级别的增、改、删,所有数据在加载时就已经确定,不可更改
(2)不支持事务
(3)支持分区存储 

2.2.5 其他

关于Hive除了上面简单总结的要点,还包括Hive的安装、把数据装载到HDFS、把HDFS的数据映射到表等内容,但个人工作中关于这些内容一般应用不到,也没有实践过,所以在本文不再过多涉及这些内容。
关于Hive,想进一步学习的话推荐下面的资源:
  • 阿里云大数据学习路线→数据仓库系统Hive

  • 《Hive实战》

  • 百度搜索:Hive学习之路

3 Hive SQL

如在「Hive SQL语法分类」部分描述,本模块重点总结DQL语法、函数相关内容;语法和函数只是最基础的内容,还需要结合应用场景把语法/函数应用起来,所以会举一些数据化运营场景的应用案例,训练SQL程序的编写思路。

3.1 DQL语法

3.1.1 数据检索

1、SELECT:指定要选出的列
2、AS:字段别名
3、DISTINCT:检索字段去重

3.1.2 检索数据处理

1、ORDER BY:
  • ASC-升序;

  • DESC-降序。

2、LIMIT 位置,条数:限制返回记录的条数 

3.1.3 数据过滤

1、WHERE操作符:
  • =、!=(<>)、<、<=、>、>=:用来做比较;

  • between…and:表示一个区间,两边都可以取到;

  • in、not  in:in 表示在集合中;not in 表示不在集合中;

  • is (not) null:判断是否为空;

2、LIKE通配符:
  • 百分号(%)通配符:表示任意字符;

  • 下划线(_)通配符:只匹配单个字符;

3.1.4 多表查询

1、子查询
(1)利用子查询进行过滤 
(2)作为计算字段使用子查询 
2、组合查询
(1)什么是组合查询(复合查询 并操作):执行多次查询,多条select语句,并将结果作为单个查询结果返回;
  • union all:包含重复行;

  • union:取消重复行; 

3、联结查询
(1)什么场景用联结查询? 
当要查询的数据存在多张表时,或者需要多个查询语句组合查询时,可通过连接查询进行数据的查询。
(2)为什么要使用联结查询?
  • 不重复存储信息,不浪费空间;

  • 数据变动、处理数据更加简单;

(3)联结查询的一般结构 
(4)联结方式:
  • inner join 内联结:保留两边表都有的记录;

  • left join  左联结:保留左边表的信息,右边表没有匹配上的字段显示为null;

  • right join 右联结:保留右边表的信息,左边表没有匹配上的字段显示为null;

  • full join  全联结:左右两边表的信息都保留,没有匹配上的字段显示为null;

  • left semi join 左半联结:JOIN 子句中右边的表只能在 ON 子句中设置过滤条件,在 WHERE 子句、SELECT 子句或其他地方过滤都不行。

(5)ON条件:
  • 过滤的关键词是 ON,不是 WHERE

  • ON 后面接的是等值筛选条件 

3.1.5 执行顺序

1、 为什么要了解sql语句执行顺序
了解一个sql语句的执行顺序,可以让我们清楚到sql执行时的操作顺序,进而有利于我们更好的优化自己的sql语句,提升程序性能。举个例子: 
    --sql 1
    select a.* from table_a a left join table_b b on a.id = b.a_id where b.name = 'john';
    --sql 2
    select * from table_a where id = (select a_id from table_b where name = 'john');

    倘若说table_a和table_b都是几十几百万数据的表。而name=‘john’这个过滤字段可以查到table_b的唯一数据。那么此时,一定是sql2的执行效率要高于sql1的。为什么?通过sql执行顺序可以知道,table_a和table_b会优先执行联表操作,两个都是大表,其查询出来的结果集虚拟表也会很大。而sql2中,table_a只用通过table_b查询出来的唯一数据更快的获取到指定结果。
    2、sql语句定义和执行顺序
    (1)sql语句定义的顺序 
      (1) SELECT 
      (2)DISTINCT<select_list>
      (3) FROM <left_table>
      (4) <join_type> JOIN <right_table>
      (5)         ON <join_condition>
      (6) WHERE <where_condition>
      (7) GROUP BY <group_by_list>
      (8) WITH {CUBE|ROLLUP
      (9) HAVING <having_condition>
      (10) ORDER BY <order_by_condition>
      (11) LIMIT <limit_number>

      (2)sql语句执行顺序 
        (8) SELECT
        (9)DISTINCT<select_list>
        (1) FROM <left_table>
        (3) <join_type> JOIN <right_table>
        (2)         ON <join_condition>
        (4) WHERE <where_condition>
        (5) GROUP BY <group_by_list>
        (6) WITH {CUBE|ROLLUP}
        (7) HAVING <having_condition>
        (10) ORDER BY <order_by_list>
        (11) LIMIT <limit_number>
        • (1) FROM:对FROM子句中的左表<left_table>和右表<right_table>执行笛卡儿积,产生虚拟表VT1;

        • (2) ON: 对虚拟表VT1进行ON筛选,只有那些符合<join_condition>的行才被插入虚拟表VT2;

        • (3)JOIN: 如果指定了OUTER JOIN(如LEFT OUTER JOIN、RIGHT OUTER JOIN),那么保留表中未匹配的行作为外部行添加到虚拟表VT2,产生虚拟表VT3。如果FROM子句包含两个以上的表,则对上一个连接生成的结果表VT3和下一个表重复执行步骤1~步骤3,直到处理完所有的表;

        • (4)WHERE: 对虚拟表VT3应用WHERE过滤条件,只有符合<where_condition>的记录才会被插入虚拟表VT4;

        • (5)GROUP By: 根据GROUP BY子句中的列,对VT4中的记录进行分组操作,产生VT5;如果应用了group by,那么后面的所有步骤都只能得到的vt5的列或者是聚合函数(count、sum、avg等)。原因在于最终的结果集中只为每个组包含一行;

        • (6)CUBE|ROllUP: 对VT5进行CUBE或ROLLUP操作,产生表VT6;

        • (7)HAVING: 对虚拟表VT6应用HAVING过滤器,只有符合<having_condition>的记录才会被插入到VT7;

        • (8)SELECT: 第二次执行SELECT操作,选择指定的列,插入到虚拟表VT8中;

        • (9)DISTINCT: 去除重复,产生虚拟表VT9;

        • (10)ORDER BY: 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10;

        • (11)LIMIT: 取出指定街行的记录,产生虚拟表VT11,并返回给查询用户 。

        3.2 Hive SQL函数

        Hive SQL是数据查询中高频应用的内容,但是又非常的多,记忆起来很麻烦,而且也没必要全部记住,应用的时候去Hive函数先去查询即可。故下面仅整理出个人之前遇到过、感觉应用频率相对高的函数:

        3.2.1 日期函数

        将时间的秒值转换成format格式 
          from_unixtime(bigint unixtime[, string format])
          from_unixtime(1250111000,"yyyy-MM-dd") 得到2009-03-12
          format可为“yyyy-MM-dd hh:mm:ss”,“yyyy-MM-dd hh”,“yyyy-MM-dd hh:mm”等等
          按指定格式返回时间date 
            date_format(date/timestamp/string ts, string fmt)
            date_format("2016-06-22","MM-dd")=06-22
            将格式为yyyy-MM-dd HH:mm:ss的时间字符串转换成时间戳 
              unix_timestamp(string date)
              unix_timestamp('2009-03-20 11:30:01') = 1237573801
              返回时间字符串的日期部分 
                to_date(string timestamp)
                to_date("1970-01-01 00:00:00") = "1970-01-01"
                返回时间字符串的年份部分 
                  year(string date)
                  year("1970-01-01") = 1970
                  year("1970-01-01 00:00:00") = 1970
                  返回时间字符串的月份部分 
                    month(string date)
                    month("1970-11-01") = 11
                    month("1970-11-01 00:00:00") = 11
                    返回时间字符串的天 
                      day(string date)
                      day("1970-11-01") = 1
                      day("1970-11-01 00:00:00") = 1


                      hour(string date)
                      minute(string date)
                      econd(string date)
                      返回时间字符串位于一年中的第几个周内 
                        weekofyear(string date)
                        weekofyear("1970-11-01 00:00:00") = 44
                        weekofyear("1970-11-01") = 44
                        计算开始时间startdate到结束时间enddate相差的天数 
                          datediff(string enddate, string startdate)
                          datediff('2009-03-01', '2009-02-27') = 2
                          从开始时间startdate加上days 
                            date_add(string startdate, int days)
                            date_add('2008-12-31', 1) = '2009-01-01'.
                            从开始时间startdate减去days 
                              date_sub(string startdate, int days)
                              date_sub('2008-12-31', 1) = '2008-12-30'
                              --
                              CURRENT_DATE() - INTERVAL '1' DAY
                              返回当前时间日期 
                                current_date()=2021-07-08
                                返回当前时间戳 
                                  current_timestamp=2021-07-08 16:25:54.226

                                  3.2.2 字符串函数

                                  对二进制字节码或字符串按次序进行拼接 
                                    concat(string|binary A, string|binary B...)
                                    concat('foo', 'bar') 返回 'foobar'
                                    与concat()类似,但使用指定的分隔符喜进行分隔 
                                      SELECT CONCAT_WS(',','First name','Last Name' )  返回  First name, Last Name   
                                      从指定路径上的JSON字符串抽取出JSON对象,并返回这个对象的JSON格式,如果输入的JSON是非法的将返回NULL,注意此路径上JSON字符串只能由数字、字母、下划线组成且不能有大写字母和特殊字符,且key不能由数字开头,这是由于Hive对列名的限制。
                                        get_json_object(string json_string, string path)
                                        --
                                        对于jsonArray(json数组),如person表的xjson字段有数据:
                                        [{"name":"王二狗","sex":"男","age":"25"},{"name":"李狗嗨","sex":"男","age":"47"}]
                                        --
                                        取出第一个json对象,那么hive sql为:SELECT get_json_object(xjson,"$.[0]") FROM person;
                                        结果是:{"name":"王二狗","sex":"男","age":"25"}
                                        --
                                        取出第一个json的age字段的值:SELECT get_json_object(xjson,"$.[0].age") FROM person;
                                        结果:25
                                        查找字符串str中子字符串substr出现的位置,如果查找失败将返回0,如果任一参数为Null将返回null,注意位置为从1开始的 
                                          instr(string str, string substr)
                                          select instr("abcde",'b'),结果是2
                                          返回字符串的长度 
                                            length(string A)
                                            对于字符串A,从start位置开始截取字符串并返回 
                                              substr(string|binary A, int start)
                                              substr('foobar', 4) 返回 'bar'
                                              对于二进制/字符串A,从start位置开始截取长度为length的字符串并返回
                                                substr(string|binary A, int start, int len)
                                                substr('foobar', 4, 1) 返回 'b'

                                                3.2.3 条件函数

                                                如果testCondition 为true就返回valueTrue,否则返回valueFalseOrNull  
                                                  if(boolean testCondition, T valueTrue, T valueFalseOrNull)
                                                  select IF(1=1,'TRUE','FALSE') 返回 'True'
                                                  select IF(1=1, 'working', 'not working') 返回'working'
                                                  如果a=b就返回c,a=d就返回e,否则返回f  
                                                    CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END
                                                    CASE 4 WHEN 5  THEN 5 WHEN 4 THEN 4 ELSE 3 END 将返回4
                                                    如果a=ture就返回b,c= ture就返回d,否则返回e 
                                                      CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END
                                                      CASE WHEN  5>0  THEN 5 WHEN 4>0 THEN 4 ELSE 0 END 将返回5
                                                      CASE WHEN  5<0  THEN 5 WHEN 4<0 THEN 4 ELSE 0 END 将返回0

                                                      3.2.4 类型转换函数

                                                      将expr转换成type类型 
                                                        cast(expr as <type>)
                                                        cast("1" as BIGINT) 将字符串1转换成了BIGINT类型,如果转换失败将返回NULL
                                                        event_day  <= CAST('2020-05-01' AS date)

                                                        3.2.5 聚合函数

                                                        统计总行数,包括含有NULL值的行 
                                                          count(*)
                                                          统计提供非NULL的expr表达式值的行数 
                                                            count(expr)
                                                            统计提供非NULL且去重后的expr表达式值的行数 
                                                              count(DISTINCT expr[, expr...])
                                                              求指定列的和 
                                                                sum(col)
                                                                求去重后的列的和 
                                                                  sum(DISTINCT col)
                                                                  求指定列的平均值 
                                                                    avg(col)
                                                                    求去重后的列的平均值 
                                                                      avg(DISTINCT col)
                                                                      求指定列的最小值 
                                                                        min(col)
                                                                        求指定列的最大值 
                                                                          max(col)
                                                                          关于Hive SQL内置函数,想进一步学习的话推荐下面的资源:Hive学习之路 (九)Hive的内置函数

                                                                          3.2.6 窗口函数

                                                                          1、什么是窗口?
                                                                          可以把“窗口”(windows)这个理解一个集合,一个窗口就是一个集合,在统计分析中有需要不同的「窗口」,比如一个部门分成不同组,在统计时会按组进行平均、排名等操作。再比如,在一些像时间这种有顺序的数据,我们可能按每年分成一组,再进行求平均数等计算。 
                                                                          2、窗口函数语法
                                                                          窗口函数(Windowing functions)基本语法: 
                                                                            xxx() over(PARTITION by yyy ORDER by zzz)
                                                                            其中:
                                                                            • xxx 为函数名

                                                                            • OVER() 括号中的内容这窗口内容

                                                                            • PARTITION by 后的为分组的字段,划分的范围被称为窗口

                                                                            • ORDER by 后决定着窗口范围内数据的排序方式

                                                                            有关语法的详细介绍请参考:https://www.gairuo.com/p/sql-window
                                                                            3、窗口函数类型
                                                                            (1)序号:关于排序后的序号显示功能有以下三个,它们都没有参数
                                                                            • row_number():不间断,序号不重复,如 1、2、3、4、5

                                                                            • rank():间断,相同值同序号,如 1、1、3、4、5

                                                                            • dense_rank():不间断,相同值同序号,如 1、1、2、3、3

                                                                            (2)首尾值
                                                                            • first_value(expr):求分组内第一个值

                                                                            • last_value(expr):求分组内最后一个值

                                                                            (3)聚合
                                                                            • sum(expr):求分组内从起点到当前行的累计值

                                                                            • avg(expr):求分组内从起点到当前行的平均值

                                                                            • min(expr):求分组内从起点到当前行的最小值

                                                                            • max(expr):求分组内从起点到当前行的最大值 

                                                                            (4)偏移
                                                                            • lag(expr [, offset] [, default]): 向下位移,意思是滞后

                                                                            • lead(expr [, offset] [, default]): 向上位移,意思是超前

                                                                            3.3 Hive SQL应用案例

                                                                            了解Hive的原理,掌握Hive SQL的语法、函数固然重要,因为这些是基础;但如果没有编写SQL程序的思维,仍然无法自己写出SQL程序,所以下面列举了一些数据化运营场景的SQL应用案例(指标、标签),帮助训练编写SQL程序的思维,供参考:

                                                                            3.3.1 用户过去7天订单实付金额总和 

                                                                              SELECT 
                                                                                  user_id,
                                                                                  distinct_id,
                                                                                  (CASE
                                                                                      WHEN c < 1000 THEN '1000以下(不含1000元)'
                                                                                      WHEN c < 2000 THEN '1000~2000(不含2000)'
                                                                                      WHEN c < 5000 THEN '<2000~5000(不含5000)'
                                                                                      ELSE '5000元以上(含5000元)'
                                                                                  END) as "过去7天订单实付金额总和"
                                                                              FROM (
                                                                                  SELECT user_id, max(distinct_id) AS distinct_id, sum(order_actual_amount) AS c FROM events
                                                                                  WHERE date BETWEEN CURRENT_DATE() - INTERVAL '7' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'PayOrder'
                                                                                  GROUP BY 1
                                                                              )a

                                                                              3.3.2 用户首次支付订单的距今天数 

                                                                                SELECT user_id,distinct_id,DATEDIFF(now(), time) AS "距今天数"
                                                                                FROM
                                                                                (SELECT user_id, max(distinct_id) AS distinct_id, MAX(time) AS time
                                                                                    FROM events
                                                                                    WHERE date BETWEEN CURRENT_DATE() - INTERVAL '91' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'PayOrder'
                                                                                    group by 1
                                                                                ) a

                                                                                3.3.3 支付订单金额高于300元的订单中,哪个一级分类最受客户自己偏爱 

                                                                                  SELECT user_id, distinct_id, first_commodity
                                                                                  FROM
                                                                                  (SELECT user_id, distinct_id, first_commodity, row_number() over(partition by user_id order by cn desc) AS "最受客户自己偏好的商品一级分类"
                                                                                  FROM
                                                                                  (SELECT user_id, max(distinct_id) AS distinct_id, first_commodity, COUNT(event) AS cn
                                                                                  FROM events
                                                                                  WHERE date BETWEEN CURRENT_DATE() - INTERVAL '91' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'PayOrderDetail'
                                                                                  GROUP BY user_id, first_commodity
                                                                                  having sum(total_price_of_commodity) > 300
                                                                                  )a1
                                                                                  )a2
                                                                                  WHERE "最受客户自己偏好的商品一级分类" < 2

                                                                                  3.3.4 过去90天内,登录App次数分布 

                                                                                    SELECT user_id, distinct_id, "登录App次数"
                                                                                    FROM
                                                                                    (
                                                                                    SELECT user_id, max(distinct_id) AS distinct_id, COUNT(*) AS "登录App次数"
                                                                                    from events
                                                                                    WHERE date BETWEEN CURRENT_DATE() - INTERVAL '91' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'LoginButtonClick'
                                                                                    GROUP BY user_id
                                                                                    ) a

                                                                                    3.3.5 新增用户数(昨日做过App激活,并且是首次访问) 

                                                                                      SELECT count(*)
                                                                                      FROM events
                                                                                      WHERE date BETWEEN CURRENT_DATE() - INTERVAL '1' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'AppInstall' AND $is_first_day = 1

                                                                                      3.3.6 连续活跃用户(昨日做过App启动,同时满足前天做过App启动) 

                                                                                        SELECT COUNT(1)
                                                                                        from
                                                                                        (
                                                                                        SELECT user_id
                                                                                        FROM events
                                                                                        WHERE date BETWEEN CURRENT_DATE() - INTERVAL '1' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'AppInstall'
                                                                                        )a
                                                                                        inner join
                                                                                        (
                                                                                        SELECT user_id
                                                                                        FROM events
                                                                                        WHERE date BETWEEN CURRENT_DATE() - INTERVAL '2' day  AND CURRENT_DATE() - INTERVAL '2' day  AND event = 'AppInstall'
                                                                                        )b on a.user_id = b.user_id

                                                                                        3.3.7 流失用户(昨日之前做过App启动,但昨日没有做过启动App) 

                                                                                          SELECT COUNT(1)
                                                                                          from
                                                                                          (
                                                                                          SELECT user_id
                                                                                          FROM events
                                                                                          WHERE date BETWEEN '2018-01-01' AND CURRENT_DATE() - INTERVAL '2' day  AND event = 'AppInstall'
                                                                                          )a
                                                                                          left join
                                                                                          (
                                                                                          SELECT user_id
                                                                                          FROM events
                                                                                          WHERE date BETWEEN CURRENT_DATE() - INTERVAL '1' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'AppInstall'
                                                                                          )b on a.user_id = b.user_id
                                                                                          WHERE b.user_id is null

                                                                                          3.3.8 回流用户(昨日做过App启动,且不是首次,同时满足前天没做过App启动) 

                                                                                            SELECT COUNT(1)
                                                                                            from
                                                                                            (
                                                                                            SELECT user_id
                                                                                            FROM events
                                                                                            WHERE date BETWEEN CURRENT_DATE() - INTERVAL '1' day  AND CURRENT_DATE() - INTERVAL '1' day  AND event = 'AppInstall' AND $is_first_day = 0
                                                                                            )a
                                                                                            left join
                                                                                            (
                                                                                            SELECT user_id
                                                                                            FROM events
                                                                                            WHERE date BETWEEN CURRENT_DATE() - INTERVAL '2' day  AND CURRENT_DATE() - INTERVAL '2' day  AND event = 'AppInstall'
                                                                                            )b on a.user_id = b.user_id
                                                                                            WHERE b.user_id is null

                                                                                            4 Hive SQL性能优化

                                                                                            对产品经理而言,Hive SQL性能优化应该算是进阶的内容,因为产品、项目开发过程中,大数据开发同事会考虑性能优化方案。不过产品经理作为整个项目中最应该理解业务的人,如果在参与性能优化方案研讨的时候提出自己的见解,肯定会达到事半功倍的效果。
                                                                                            近期项目中遇到了数据查询性能的问题,因为数据量很大(用户记录是千万级、事件记录是亿级),并且一次要执行多个指标结果的在线计算(在presto进行计算查询)。所以不得不反思,如果在设计和查询时重视性能问题的话,应该想到更好的设计方案。
                                                                                            基于上述的两点原因,本次专题的学习也简单总结了Hive SQL性能优化的知识,将会从如下4个方面展开介绍:

                                                                                            4.1 设计合适的数据模型

                                                                                            数据模型就是数据组织和存储方法,合适的数据模型可以支持快速查询所需的数据。例如常见用户运营数据平台的多维事件模型(event表+user表),对数据增删改的实时性要求不强,但对历史数据的分析需求强,因此就把行为事件的维度信息冗余到了event表上,尽量避免查询时进行多表join运算,有效提升了数据查询的速度。
                                                                                            常见数据模型还有维度模型、雪花模型等,关于数据模型的知识点很多,不便在本文展开,后续会系统学习、总结下有关数据模型的知识点。 

                                                                                            4.2 优化Hive SQL查询语句

                                                                                            因为DQL语言会在工作中高频应用,所以前面的内容有重点介绍过DQL语法相关内容,在本部分再进一步总结下在编写Hive SQL查询语句时,如何提升查询效率。会从如下几个方面展开:

                                                                                            4.2.1 列裁剪和分区裁剪

                                                                                            当列很多或者数据量很大时,如果select *或者不指定分区,全列扫描和全表扫描效率都很低。可以通过只读取需要的列(列裁剪)、只读取需要的分区(分区裁剪)来提升查询的效率。 

                                                                                            4.2.2 谓词下推

                                                                                            将SQL语句中的where谓词逻辑都尽可能提前执行,减少下游处理的数据量。例如以下HiveSQL语句,对forum_topic做过滤的where语句写在子查询内部,而不是外部。 

                                                                                            4.2.3 sort by代替order by

                                                                                            HiveSQL中的order by与其他SQL方言中的功能一样,就是将结果按某字段全局排序,这会导致所有map端数据都进入一个reducer中,在数据量大时可能会长时间计算不完,如果使用sort by,那么还是会视情况启动多个reducer进行排序,并且保证每个reducer内局部有序。
                                                                                            为了控制map端数据分配到reducer的key,往往还要配合distribute by一同使用。如果不加distribute by的话,map端数据就会随机分配到reducer。
                                                                                            举个例子,假如要以UID为key,以上传时间倒序、记录类型倒序输出记录数据: 

                                                                                            4.2.4 group by代替distinct

                                                                                            当要统计某一列的去重数时,如果数据量很大,count(distinct)就会非常慢,原因与order by类似,count(distinct)逻辑只会有很少的reducer来处理。这时可以用group by来改写: 
                                                                                            但是这样写会启动两个MR job(单纯distinct只会启动一个),所以要确保数据量大到启动job的overhead远小于计算耗时(启动MR jib耗时),才考虑这种方法。 

                                                                                            4.2.5 join优化

                                                                                            1、build table(小表)前置
                                                                                            在最常见的hash join方法中,一般总有一张相对小的表和一张相对大的表,小表叫build table,大表叫probe table。如下图所示: 
                                                                                            Hive在解析带join的SQL语句时,会默认将最后一个表作为probe table,将前面的表作为build table并试图将它们读进内存。如果表顺序写反,probe table在前面,引发OOM的风险就高了。
                                                                                            在维度建模数据仓库中,事实表就是probe table,维度表就是build table。假设现在要将日历记录事实表和记录项编码维度表来join: 
                                                                                            2、多表join时key相同的处理
                                                                                            多表join时,若key相同,会将多个join合并为一个MR job来处理,例如: 
                                                                                            如果上面两个join的条件不相同,比如改成a.event_code = c.event_code,就会拆成两个MR job计算。 
                                                                                            3、优化SQL处理join数据倾斜_空值或无意义值
                                                                                            这种情况很常见,比如当事实表是日志类数据时,往往会有一些项没有记录到,我们视情况会将它置为null,或者空字符串、-1等。如果缺失的项很多,在做join时这些空值就会非常集中,拖累进度。因此,若不需要空值数据,就提前写where语句过滤掉。需要保留的话,将空值key用随机方式打散,例如将用户ID为null的记录随机改为负值: 
                                                                                            以上内容来自:Hive/HiveSQL常用优化方法全面总结。此处只摘抄了自己能理解、可能用到的部分内容,希望自己应用时能够注意。

                                                                                            4.3 合理进行Hive配置、MapReduce分配

                                                                                            关于Hive SQL性能优化还有合理进行Hive配置、MapReduce分配,但这些内容自己应该不会自己去实践,所以没有花过多时间去研究,感兴趣的同学同样可以去阅读上面提到的文章。

                                                                                            4.4 预计算与缓存计算结果

                                                                                            有些场景即便选择了合适的数据模型、优化了Hive SQL查询语句、分配了合适的MapReduce,但由于数据量很大,仍然无法快速计算出结果。下面介绍几个常用的优化方法:

                                                                                            4.4.1 定时预计算

                                                                                            对于查询条件相对固定,且条件组合类型不是非常多的场景,可以设置预计算任务,比如每天凌晨完成前一天结果的预计算,等用户在前端进行查询时,可以快速显示出提前计算好的结果。

                                                                                            4.4.2 在中间表完成数据的初步聚合

                                                                                            有的场景查询条件虽然固定,但是条件组合类型非常多,比如有时间(昨日、本周、上周、本月、上月、过去3个月、过去半年、本年等)、地区(省份、城市)、品牌(一级分类、二级分类)3个维度,但是组合类型却非常多,如果提前预计算的话计算量非常大,程序写起来复杂,而且会消耗很多计算资源。
                                                                                            这个时候可以从数据展示粒度的角度考虑优化方案,比如展示结果最细粒度是到天,但是原始数据记录的是到毫秒级的明细数据,可以在中间表完成从毫秒粒度到天粒度数据的预计算,这样可以大大减少查询时候的数据记录数,保证快速计算出结果。
                                                                                            这种优化方式在一些大屏产品里比较适用,因为大屏一般是给领导看,领导关心的是一些粗粒度的大盘指标。 

                                                                                            4.4.3 其他

                                                                                            如果无法进行固定条件的预计算、也无法采用数据粒度的转变,还想要快速完成计算结果的预计算,还可以从下面的3个方面考虑优化:
                                                                                            首先,可以通过在用户界面增加合理的正在计算的提示,缓解用户等待的焦虑;
                                                                                            然后,对于有多个结果的计算,尽量分开计算,比如一个界面有50个指标,可以拆成10组,优先计算界面上靠前的第一组的5个指标,后面的排队等待计算;
                                                                                            最后,可以考虑试用Redis缓存下计算结果,这样可以保证下次查询、或者其他用户查询相同条件的结果时,快速显示结果。

                                                                                            5 总结

                                                                                            综上,通过本文系统地学习、总结了如下内容:
                                                                                            1、Hive初识:对Hadoop、Hive的特点、原理、应用有所了解,以便能够能够理解有关大数据存储、计算话题;
                                                                                            2、Hive SQL:对语法、函数的知识点进行总结,构建了有关Hive SQL的知识体系,方便理解记忆和应用;另外通过编写一些数据化运营场景的案例,训练了编写SQL程序的思维;
                                                                                            3、Hive SQL性能优化:近期踩了性能问题的坑,希望对性能优化有些了解,方便以后在工作中应用实践。

                                                                                            参考资料:
                                                                                            [1] 《Hive实战》
                                                                                            [2]  阿里云大数据学习路线:数据仓库系统Hive
                                                                                            [3]  阿里云大数据学习路线:Hadoop快速入门
                                                                                            [4]  深入浅出大数据:到底什么是Hadoop?
                                                                                            [5]  阿北老师:7天SQL训练营
                                                                                            [6]  陈旸老师:SQL必知必会
                                                                                            [7]  盖若老师:Hive SQL 教程
                                                                                            [8]  扎心了老铁:Hive学习之路
                                                                                            [9]  Hive SQL语法总结
                                                                                            [10]  sql语句执行顺序,你理解了吗
                                                                                            文章转载自王木工,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                                            评论