Hive学习笔记-HiveSQL(2)-数据操控语言DML与数据查询语言DQL
Hive SQL DML
Load 加载数据
在前面的笔记中我们看到当我们在Hive中建表成功之后,在HDFS上就会创建一个对应的文件夹,我们可以利用hadoop fs -put
直接将数据上传到HDFS上,Hive会自动完成表和底层数据的映射。
这种方式能够达到目标,但是Hive推荐我们使用Load命令来将数据加载到表中。Load命令能够将数据文件移动到Hive表中对应的位置,在过程中不会对数据内容进行任何转换的操作,保持数据原原本本的状态。
语法规则如下:
1 |
|
filepath
:表示待移动的数据的位置,可以是相对路径、绝对路径和具有schema的完整URI。并且需要结合local
关键字来确定数据文件的位置,可能是本地数据文件,HDFS数据文件或者其他文件系统下的文件- 如果没有指定schema,则schema由Hadoop配置文件中的参数
fs.default.name
指定 - local的本地指的是HiveServer2服务所在的本地Linux文件系统
- 如果没有指定schema,则schema由Hadoop配置文件中的参数
overwrite
:覆盖目标表或者分区中已经存在的数据partition
:用于手动指定分区(静态分区操作)inputformat
和serde
:用于指定输入格式,例如Text、Orc等
实际常见的数据加载情况有如下的可能:
- 从本地文件系统中加载数据:使用local关键字,本质是
hadoop fs -put
上传操作- 从HDFS上加载数据:不使用local关键字,本质是
hadoop fs -mv
移动操作
在Hive3.0之后,Load提供了新特性,在某些场合下会将Load重写为insert as select。
具体来说,我们可以利用load来插入分区表。如果在load的时候,表具有分区,但是我们没有在命令中指定分区,则会将load转化为insert as select,并会假定最后一列作为分区列。这样的话就会要求我们文件的实际列数多于表中的列数,因为最后的列会作为分区列。这也是一种动态分区加载的形式
Insert 插入数据
在RDBMS中,我们通常使用insert+values的方式来向表中插入数据,并且执行速度都非常快。在Hive中,我们也可以使用insert+values的方式来插入数据,但是指定的速度非常慢,原因在于Hive的底层会使用MapReduce任务来将数据写入Hive表中,因此我们常用的数据加载方式是使用Load命令而不是insert。
我们更多的使用方法是insert+select,这表示我们将后面查询返回的结果作为内容插入到指定表中,语法如下:
1 |
|
同时Hive中还提供多重插入的功能,Multiple inserts。多重插入指的是一次扫描,多次插入。对应的场景为,我们可能要将一张表中的不同内容插入到不同的表中,按照已有的知识,我们可以使用多个insert+select语句,这样会多次扫描同一张表。多重插入的思想就是减少扫描的次数,在一次扫描中完成多次的insert操作,应用举例如下:
1 |
|
前面在分区表中,我们也提到过动态分区加载的手段,也是通过insert+select来完成的,详细的细节可以参考前面的的笔记。
另外,我们可以利用insert来进行数据的导出,将select查询的结果导出成文件存放在文件系统中,语法如下:
1 |
|
- 需要注意这里的overwrite会覆盖我们指定的目录
- local表示指定本地文件系统下的目录
- 目录可以是完整的指定了scheme的URI,如果没有指定,则同上,使用Hadoop配置变量
fs.default.name
来决定 - 写入文件系统的数据会被序列化为文本,默认列之间使用
\001
分隔,行之间使用换行符分隔。列也可能是复杂数据类型,则默认会序列化成json的格式。我们也可以在导出的时候自定义指定不同的换行符
事务表相关操作
除了Insert之外,常见的DML还有update和delete,但是在前面的笔记中我们也提到,Hive早期是不支持这些操作的,而是在后续的版本迭代中慢慢引入了事务表的支持。在事务表上,我们才可以使用update和delete操作。事务表的背景、创建和局限这里也不再赘述,我们主要关注这些操作的具体原理。
Hive的文件存储在HDFS上,但是HDFS并不支持对文件的任意修改,则必须采用另外的手段来达成对应的效果。
事务的操作,会以HDFS文件作为原始的基础数据,用delta文件夹来保存事务操作,记录增量数据。正在执行的事务,会用一个staging开头的文件夹来维护,执行结束之后,得到delta文件,对应事务执行的结果。每执行一次事务操作,都会得到这样一个delta增量文件夹,当访问Hive数据的时候,会根据HDFS原始数据和delta增量文件进行合并,查询最新的数据。
delta文件夹的具体命名格式为delta前缀_minID_maxID_stmID
- delta前缀:如果是insert和update,前缀为delta;如果操作是delete,前缀为delete_delta(update语句采用split-update特性,相当于先删除,后插入)
- minID和maxID:写事务的ID范围
- stmID:语句ID
在Hive中,会为写事务(insert、update等)创建一个写事务ID,该ID在表范围内唯一;同时有一个语句ID,是在当一个事务中有多条写入语句时使用的,用作唯一标识。
随着表修改操作的增多,会产生越来越多的delta增量文件,这个时候应该需要进行文件的合并来保证性能。合并器Compactor就是一套在Hive Metastore内部运行,用来支持ACID系统的后台进程。所有合并在后台完成,并不会阻止数据的并发读写。合并后,系统将等待所有旧文件读操作完成之后,删除旧文件。
合并操作分为两种,Minor Compaction和Major Compaction
- Minor Compaction:小合并,将一组delta增量文件重写为单个增量文件,默认触发条件为10个delta文件
- Major Compaction:大合并,将一个或多个增量文件以及基础文件重写为新的基础文件,默认触发条件为delta文件相应于基础文件超过10%
Hive SQL DQL
select 语法树
Hive中的DQL数据查询语言主要指的就是select查询操作,select的语法树如下所示(表名和列名不区分大小写)
select 基础查询语法
select中的基础查询语法包括如下关键字:
1 |
|
这些基础语法的用法与MySQL中的语法类似,这里只简单介绍一下
默认使用all表示不去重,distinct表示去重
分区裁剪:在查询的时候可以指定分区进行查询,减少全表扫描(可以将分区的虚字段当成普通字段使用)
出现在group by中select的字段,要么是group by分组的字段,要么是被聚合函数应用的字段
where和having的含义与用法与SQL相同
limit用于限制select语句返回的行数
执行顺序:from > where > group > having > order > select
在select语句中可以使用正则表达式来匹配字段
1
2
3
4
5--查询匹配正则表达式的所有字段
set hive.support.quoted.identifiers=none;
--表示反引号不再解释为其他含义,被解释为正则表达式
select `^c.*` from table_name
--查询所有以c开头的列字段
select 排序相关语法
Hive SQL中的排序相关语法在语法树中主要体现为以下关键字:
1 |
|
order by
首先是order by语法。Hive SQL中的order by语法也类似于标准SQL中的order by语法,它会对输出的结果进行全局排序。
这也就意味着当底层是使用MapReduce引擎执行的时候,只有一个Reduce Task执行。如果输出的行数太大,会导致需要很长时间才能完成全局排序
默认的排序顺序为升序asc,也可以指定为降序desc
在Hive2.1.0之后,也支持在order by子句中为每个列指定null类型结果排序顺序,关键字为nulls first和nulls last。asc顺序的默认空排序顺序为nulls first、desc顺序的默认空排序顺序为nulls last。
强烈建议将limit于order by配合使用,以避免数据集行数过大
当hive.mapred.mode设置为strict严格模式的时候,使用不带limit的order by会引发异常
cluster by
前面的order by可以对数据进行全局排序,cluster by达到的效果就是根据字段将数据进行分组,然后每组内再根据该字段进行正序排序(只能正序)。总结来说就是根据同一个字段划分,之后再分区内部正序排序
这里就会涉及到两个重要的问题,如何划分以及划分成多少个区域
划分的规则与分桶表相同,利用hash散列函数。而划分成多少个区域则取决于启动的Reduce Task任务的个数。如果我们不指定Reduce Task任务的话,默认情况只启动一个Reduce Task,这种情况就和正序的order by类似;当然我们也可以手动指定Reduce Task的个数
1 |
|
distribute by sort by
从前面cluster by的介绍可以看出,虽然达到了分区的效果,但是还是有一定的局限性,即分区和排序只能按照同一个字段进行。而distribute by + sort by的效果就是分区和排序按照不同的字段,distribute by指定分区字段,sort by指定排序字段,同时可以指定正序或者倒序字段。
1 |
|
如果distribute by和sort by指定同一个字段并且使用正序排序,就和cluster by功能相同。
排序相关语法的对比:
- order by是全局排序,无论配置如何,最终启动的还是只有一个Reduce Task任务
- cluster by完成分组和排序,但是只能按照同一个字段进行,且是正序排序。分组算法为hash散列,分组个数与Reduce Task个数有关,可以手动设置,也可以是使用默认的1
- distribute by + sort by可以看作是升级版的cluster by,分组字段和排序字段可以不同
union 联合查询
union用于将来自多个select语句的结果合并成为一个结果集,后面可以使用all或者distinct关键字,表示是否删除重复的行。默认行为为distinct
SQL中的union默认行为也是distinct
union必须保证每个select语句返回的列数量和名称必须相同
1 |
|
这里需要注意的是,使用union的必须是单个select语句,如果要应用order by,sort by,cluster by,distribute by或limit的话,应该要使用子查询的语法
1 |
|
CTE使用
CTE,全称为Common Table Expressions,意为公共表表达式。这是一个临时结果集,从with子句中的指定查询派生而来。CTE定义临时结果集之后,可以当作一个普通的表来使用
1 |
|
- CTE仅在单个语句的执行范围内定义
Hive SQL join 连接操作
在Hive中目前支持6中join语法,分别是inner join(内连接)、left join(左连接)、right join(右连接)、full outer join(全外连接)、left semi join(左半开连接)、cross join(交叉连接,也叫做笛卡尔乘积)。join的条件仍然是使用on来指定
从Hive 0.13.0开始,支持隐式联接表示法。允许FROM子句连接以逗号分隔的表列表,而省略JOIN关键字。
从Hive 2.2.0开始,支持ON子句中的复杂表达式,支持不相等连接。在此之前,Hive不支持不是相等条件的联接条件。
其中内连接、左连接、右连接、全外连接这些操作和SQL中的定义都相同,我们已经很熟悉了,这里就介绍剩下的左半开连接和交叉连接。
左半开连接(left semi join)类似于内连接的逻辑,但是只返回其中在左表的字段。即最终返回的结果行是内连接得到的行,但是字段只有在左表中出现的字段。
交叉连接(cross join)则会返回两个表的笛卡尔积。
- 在SQL标准中,笛卡尔积是没有条件的inner join
- 在Hive SQL语法中,cross join后面可以使用where子句或on条件过滤,但是还是先笛卡尔积,然后根据条件过滤
join的使用注意事项:
允许使用复杂的连接表达式,支持非等值连接
同一个查询中可以连接两个以上的表
如果每个表在连接子句中使用相同的列,则Hive会将多个表上的连接转换成单个MapReduce作业
1
2
3
4
5SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)
--由于联接中仅涉及b的key1列,因此被转换为1个MR作业来执行
SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)
--会转换为两个MR作业,因为在第一个连接条件中使用了b中的key1列,而在第二个连接条件中使用了b中的key2列
--第一个map / reduce作业将a与b联接在一起,然后将结果与c联接到第二个map / reduce作业中join时最后一个表会通过Reducer进行流式传输,并在其中缓存之前的其他表(相当于是将最后一个表流式传输,进来一个join一个)。因此将大表放置在最后有助于减少Reducer阶段缓存数据所需要的内存(也是我们通常习惯的小表接大表)
在join的时候,也可以通过关键字
streamtable
显式指定需要流式传输的表。如果省略,则Hive将流式传输最右边的表1
2
3select streamtable(a) a.val, b.val c.val
from a join b on (a.key = b.key1)
join c on (c.key = b.key1)如果除了一个要连接的表之外的所有表都很小,则可以将其作为仅map作业执行(mapjoin,没有Reduce阶段)
1
2
3
4
5select mapjoin(b) a.key, a.value
from a join b on a.key=b.key
--这样就不需要启动reducer,对于a的每个Mapper,b表数据都会被完全读取。
--限制是不能执行full/right outer join b
--因为full join b或者right outer join b,b表中的一行都要去找所有的a表数据