一、Hive基本概念

  1. hive简介

    • 由 Facebook 开源用于解决海量结构化日志的数据统计工具。
    • Hive 是基于 Hadoop 的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并 提供类 SQL 查询功能。

    Hive本质:讲HQL转化成MapReduce程序。

    • 处理的数据存储再HDFS中
    • 分析数据底层的实现是MapReduce
    • 执行程序运行在Yarn上
  2. Hive架构原理

    Hive个人学习记录_hive

    • 用户接口:Client

      CLI(command-line interface)、JDBC/ODBC(jdbc 访问 hive)、WEBUI(浏览器访问 hive)

    • 元数据:Metastore

      元数据包括:表名、表所属的数据库(默认是 default)、表的拥有者、列/分区字段、 表的类型(是否是外部表)、表的数据所在目录等。

      默认存储在自带的derby数据库中。

      • derby数据库不支持多用户使用
    • Hadoop

      使用HDFS进行存储,使用MapReduce进行计算(默认情况下)

    • 驱动器:Driver

      • 解析器(SQL Parser):将 SQL 字符串转换成抽象语法树 AST,这一步一般都用第 三方工具库完成,比如 antlr;对 AST 进行语法分析,比如表是否存在、字段是否存在、SQL 语义是否有误。
      • 编译器(Physical Plan):将 AST 编译生成逻辑执行计划。
      • 优化器(Query Optimizer):对逻辑执行计划进行优化。
      • 执行器(Execution):把逻辑执行计划转换成可以运行的物理计划。对于 Hive 来 说,就是 MR/Spark。

    总结:

    ​ Hive 通过给用户提供的一系列交互接口,接收到用户的指令(SQL),使用自己的 Driver, 结合元数据(MetaStore),将这些指令翻译成 MapReduce,提交到 Hadoop 中执行,最后,将 执行返回的结果输出到用户交互接口。

  3. Hive常用交互命令

    bin/hive -help usage: hive
    -d,--define <key=value> Variable subsitution to apply to hive
    commands. e.g. -d A=B or --define A=B
    --database <databasename>   Specify the database to use
    -e <quoted-query-string>    SQL from command line
    -f <filename>   SQL from files
    -H,--help   Print help information
    --hiveconf <property=value> Use value for given property
    --hivevar <key=value>   Variable subsitution to apply to hive
    commands. e.g. --hivevar A=B
    -i <filename>   Initialization SQL file
    -S,--silent Silent mode in interactive shell
    -v,--verbose    Verbose mode (echo executed SQL to the console)
    
    • “-e”不进入 hive 的交互窗口执行 sql 语句

      bin/hive -e "select id from student;

    • “-f”执行脚本中 sql 语句

      bin/hive -f /opt/module/hive/datas/hivef.sql > /opt/module/datas/hive_result.txt

    • 在hive cli 命令窗口中查看hdfs文件系统

      dfs -ls /;

    • 查看在hive中输入的所有历史命令

      1. 进入当前用户的根目录

      2. 查看.hivehistory文件

        cat .hivehistory

  4. 参数配置方式

    1. 配置文件方式

      • 默认配置文件:hive-default.xml
      • 用户自定义配置文件:hive-site.xml

      注意:用户自定义配置会覆盖默认配置。另外,Hive 也会读入 Hadoop 的配置,因为 Hive 是作为 Hadoop 的客户端启动的,Hive 的配置会覆盖 Hadoop 的配置。配置文件的设定对本 机启动的所有 Hive 进程都有效。

    2. 命令行参数方式

      启动Hive时,可以在命令行添加-hiveconf param=value 来设定参数。

      比如:

      bin/hive -hiveconf mapred.reduce.tasks=10

      仅对本次hive启动有效

    3. 参数声明方式

      可以在HQL中使用SET关键字设定参数

      比如:

      hive (default)> set mapred.reduce.tasks=100;

      仅对本次hive启动有效

    三种设定方式的优先级依次递增,即配置文件<命令行参数<参数声明

二、Hive数据类型

  1. 基本数据类型

    Hive 数据类型 Java 数据类型 长度 例子
    TINYINT byte 1byte 有符号整数 20
    SMALINT short 2byte 有符号整数 20
    INT int 4byte 有符号整数 20
    BIGINT long 8byte 有符号整数 20
    BOOLEAN boolean 布尔类型,true 或者false TRUE FALSE
    FLOAT float 单精度浮点数 3.14159
    DOUBLE double 双精度浮点数 3.14159
    STRING string 字符系列。可以指定字 符集。可以使用单引号或者双 引号。 ‘ now is the time’
    “I can do this all day”
    TIMESTAMP 时间类型
    BINARY 字节数组
  2. 集合数据类型

    数据类型 描述 语法示例
    STRUCT 和 c 语言中的 struct 类似,都可以通过“点”符号访 问元素内容。例如,如果某个列的数据类型是 STRUCT{first STRING, last STRING},那么第 1 个元素可以通过字段.first 来 引用。 struct()
    例如 struct<street:string, city:string>
    MAP MAP 是一组键-值对元组集合,使用数组表示法可以 访问数据。例如,如果某个列的数据类型是 MAP,其中键->值对是’first’->’John’和’last’->’Doe’,那么可以 通过字段名[‘last’]获取最后一个元素 map()
    例如 map<string, int>
    ARRAY 数组是一组具有相同类型和名称的变量的集合。这些 变量称为数组的元素,每个数组元素都有一个编号,编号从 零开始。例如,数组值为[‘John’, ‘Doe’],那么第 2 个元素可以通过数组名[1]进行引用。 Array()
    例如 array

    案例:

    {
         "name": "Jerry",
         "friends": ["Mary" , "Lily"] , //列表 Array, 
         "children": { //键值 Map,
             "Lux": 18 ,
             "Zox": 19
         }
         "address": { //结构 Struct,
             "street": "Bary Dany",
             "city": "Maria"
         } 
     }
    

    对应的表:

    create table test( 
        name string,
        children map<string, int>,
        address struct<street:string, city:string>
    )
    row format delimited
    fields terminated by ','
    collection items terminated by '_'
    map keys terminated by ':'
    lines terminated by '\n';
    

    字段解释:

    row format delimited fields terminated by ','	--  列分隔符
    collection items terminated by '_'	--MAP STRUCT  和 ARRAY  的分隔符(数据分割符号) map keys terminated by ':'	-- MAP 中的 key 与 value 的分隔符
    lines terminated by '\n';	--  行分隔符
    
  3. 类型转化

    1. 隐式类型转换规则

      • 任何整数类型都可以隐式地转换为一个范围更广的类型,如 TINYINT 可以转换成

        INT,INT 可以转换成 BIGINT。

      • 所有整数类型、FLOAT 和 STRING 类型都可以隐式地转换成 DOUBLE。

      • TINYINT、SMALLINT、INT 都可以转换为 FLOAT。

      • BOOLEAN 类型不可以转换为任何其它的类型。

    2. 使用CAST操作显示进行数据类型转换

      CAST('1' AS INT)

      将字符串 '1' 转换成整数 1 ,如果转换失败,返回空值 NULL

三、DDL语言

  1. 数据库

    1. 创建数据库

      CREATE DATABASE [IF NOT EXISTS] database_name [COMMENT database_comment]
      [LOCATION hdfs_path]
      [WITH DBPROPERTIES (property_name=property_value, ...)];
      
      1. 创建一个库,增加if not exists 判断

        create database hive_db if not exists hive_db;

      2. 创建一个数据库,指定数据库在HDFS上存放的位置

        create database hive_db location '/hive.db'

    2. 查询数据库

      1. 显示数据库

        show databases;

      2. 增加过滤条件

        show databases like 'hive_db';

    3. 查看数据库详细

      1. 显示数据库信息

        desc database hive_db;

      2. 显示数据库详细信息

        desc database extended hive_db;

    4. 切换数据库

      use hive_db;

    5. 修改数据库

      可以使用ALTER DATABASE修改某个数据库的DBPROPERTIES设置键值对属性值

      alter database hive_db set dbproperties('createtime'='20170830');

    6. 删除数据库

      1. 数据库为空

        drop database if exists hive_db;

      2. 数据库不为空

        drop database hive_db cascade;

        使用cascade强制删除

    1. 创建表

      CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
      [(col_name data_type [COMMENT col_comment], ...)] [COMMENT table_comment]
      [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...)
      [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] [ROW FORMAT row_format]
      [STORED AS file_format] [LOCATION hdfs_path]
      [TBLPROPERTIES (property_name=property_value, ...)] [AS select_statement]
      

      字段说明:

      • CREATE TABLE 创建一个指定名字的表。如果相同名字的表已经存在,则抛出异常; 用户可以用 IF NOT EXISTS 选项来忽略这个异常。

      • EXTERNAL 关键字可以让用户创建一个外部表,在建表的同时可以指定一个指向实 际数据的路径(LOCATION),在删除表的时候,内部表的元数据和数据会被一起删除,而外 部表只删除元数据,不删除数据。

      • COMMENT:为表和列添加注释。

      • PARTITIONED BY 创建分区表

      • CLUSTERED BY 创建分桶表

      • SORTED BY 不常用,对桶中的一个或多个列另外排序

      • ROW FORMAT DELIMITED [FIELDS TERMINATED BY char] [COLLECTION ITEMS TERMINATED BY char] [MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char] | SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value, property_name=property_value, ...)]

      • STORED AS 指定存储文件类型 常用的存储文件类型:SEQUENCEFILE(二进制序列文件)、TEXTFILE(文本)、RCFILE(列式存储格式文件)

        如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE。

      • LOCATION :指定表在 HDFS 上的存储位置。

      • AS:后跟查询语句,根据查询结果创建表。

      • LIKE 允许用户复制现有的表结构,但是不复制数据。

    2. 管理表(内部表)

      当我们删除一个管理表的时候,Hive也会删除这个表中数据。

      • 创建普通表

        create table if not exists student( id int, name string
        )
        row format delimited fields terminated by '\t' stored as textfile
        location '/user/hive/warehouse/student';
        
      • 根据查询结果创建表

        create table if not exists student2 as select id, name from student;

      • 根据已经存在的表结构创建表

        create table if not exists student3 like student;

      • 查看表的类型

        hive (default)> desc formatted student2; 
        Table Type:	MANAGED_TABLE
        
    3. 外部表

      删除表的时候,不会将数据删除,但会将描述表的元数据信息删除。

      • 创建外部表

        create external table if not exists dept( deptno int,
        dname string, loc int
        )
        row format delimited fields terminated by '\t';
        
      • 查看表格式化数据

        hive (default)> desc formatted dept;
        Table Type:	EXTERNAL_TABLE
        

      外部表删除后,hdfs 中的数据还在,但是 metadata 中 dept 的元数据已被删除

    4. 管理表和外部表的互相转换

      alter table student set tblproperties('EXTERNAL'='TRUE')

      转换成外部表,同理设置为FALSE转换成内部表

    5. 修改表

      1. 修改表名

        ALTER TABLE table_name RENAME TO new_table_name

      2. 增加、修改、替换列信息

        • 更新列

          ALTER TABLE table_name CHANGE [COLUMN] col_old_name col_new_name column_type [COMMENT col_comment] [FIRST|AFTER column_name]
          
        • 增加和替换列

          ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name data_type [COMMENT col_comment], ...)
          

        注:ADD 是代表新增一字段,字段位置在所有列后面(partition 列前),

        REPLACE 则是表示替换表中所有字段。

      3. 增加、修改、删除表分区

        见第六章

      4. 普通表案例

        • 增加列

          hive (default)> alter table dept add columns(deptdesc string);

        • 更新列

          hive (default)> alter table dept change column deptdesc desc string;

        • 替换列

          hive (default)> alter table dept replace columns(deptno string, dname string, loc string);

        replace实际上可以做到增删改,效果等同于ADD,CHANGE

    6. 删除表

      drop table dept;

四、DML数据操作

  1. 数据导入

    1. 载入数据(Load)

      1. 语法

        hive> load data [local] inpath '数据的 path' [overwrite] into table
        table_name [partition (partcol1=val1,…)];
        
        • load data:表示加载数据
        • local:表示从本地加载数据到hive表中,否则从HDFS加载数据到hive表
        • inpath:表示加载数据的路径
        • overwrite:表示覆盖表中已有数据,否则表示追加
        • into table:表示加载到哪张表
        • table_name:表示具体的表
        • partition:表示上传到指定分区
      2. 案例

        • 创建表
        create table student(id string, name string) row format delimited fields terminated by '\t';
        
        • 加载本地文件到数据
        load data local inpath '/opt/module/hive/datas/student.txt' into table default.student;
        
        • 加载HDFS文件到hive中 上传文件
        dfs -put /opt/module/hive/data/student.txt
        /user/tomato/hive;
        
        load data inpath '/user/atguigu/hive/student.txt' [overwrite] into table default.student;
        
    2. 通过查询语句向表插入数据(Insert)

      • 创建表
      create table student_par(id int, name string) row format delimited fields terminated by '\t';
      
      • 插入单条数据
      insert into table student_par values(1,'Jack'),(2,'Mack');
      
      • 基本模式插入(根据单张表查询结果)
      insert overwrite table student_par
      select id, name from student where month='201709';
      
      • 多表(多分区)插入模式(根据多张表查询结果)
      from student
      insert overwrite table student partition(month='201707') select id, name where month='201709'
      insert overwrite table student partition(month='201706') select id, name where month='201709';
      
    3. 查询语句中创建表并加载数据(As Select)

      create table if not exists student2 
      as select id, name from student;
      
    4. 创建表时,通过Location指定加载数据路径

      create external table if not exists student5(
          id int, name string
      )
      row format delimited
      fields terminated by '\t'
      location '/student;
      
    5. Import数据到指定Hive表中

      import table student2
      from '/user/hive/warehouse/export/student';
      

      此方式,路径中的数据必须是由export导出的,才可import导入表中

  2. 数据导出

    1. Insert导出

      • 结果导出到本地

        insert overwrite local directory
        '/opt/module/hive/data/export/student'
        select * from student;
        
      • 将查询的结果格式化

        insert overwrite local directory
        '/opt/module/hive/data/export/student'
        row format delimited
        fields terminated by '\t'
        select * from student;
        

        若不格式化分隔符,则导出的结果是无法理解的分隔符号

      • 不加local将导入HDFS中

        insert overwrite directory '/user/atguigu/student2' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
        select * from student;
        
    2. 使用Hadoop命令导出到本地

      dfs -get /user/hive/warehouse/student/student.txt /opt/module/data/export/student3.txt;

    3. 还可用Shell命令导出

      bin/hive -e 'select * from default.student;' >
      /opt/module/hive/data/export/student4.txt;
      

      将结果追加到本地文件中

    4. 通过Export导出

      export table default.student to '/user/student'
      
    5. 其他方法

      • Sqoop
  3. 清除表中的数据(Truncate)

    truncate table student;

    注意:Truncate 只能删除管理表,不能删除外部表中数据

五、查询语句

  1. 基本语法与MYSQL一致

  2. 模糊查询的不同点

    1. Like和RLike

      • 选择条件可以包含字符或数字

        % 代表零个或多个字符(任意个字符)。

        _ 代表一个字符。

      • RLIKE子句

        RLIKE 子句是 Hive 中这个功能的一个扩展,其可以通过 Java 的正则表达式这个更强大 的语言来指定匹配条件。

    2. 案例

      • 查找名字以A开头的员工信息

        select * from emp where ename LIKE 'A%';

      • 查找名字中第二个字母为A点的员工信息

        select * from emp where ename LIKE '_A%';

      • 查找名字中带有A的员工信息

        select * from emp where ename like '%A%';

        select * from emp where ename rlike '[A]';

  3. JOIN语句

    与MYSQL基本一致

    • 多表连接查询
    SELECT e.ename, d.dname, l.loc_name FROM	emp e
    JOIN	dept d
    ON	d.deptno = e.deptno JOIN	location l
    ON	d.loc = l.loc;
    

    ​ 大多数情况下,Hive 会对每对 JOIN 连接对象启动一个 MapReduce 任务。本例中会首先 启动一个 MapReduce job 对表 e 和表 d 进行连接操作,然后会再启动一个 MapReduce job 将 第一个 MapReduce job 的输出和表 l;进行连接操作。

    ​ 优化:当对 3 个或者更多表进行 join 连接时,如果每个 on 子句都使用相同的连接键的 话,那么只会产生一个 MapReduce job。

  4. 排序

    1. 全局排序(Order By)

      Order By:全局排序,只有一个Reducer

      • ASC:升序
      • DESC:降序
    2. 每个Reduce内部排序(Sort By)

      Sort By:对于大规模的数据集 order by 的效率非常低。在很多情况下,并不需要全局排 序,此时可以使用 Sort By

      Sort by 为每个 reducer 产生一个排序文件。每个 Reducer 内部进行排序,对全局结果集 来说不是排序

      1. 设置reduce个数

        set mapreduce.job.reuces=3;

      2. 按照部门编号降序查看员工信息

        select * from emp sort by deptno desc;

    3. 分区(Distribute By)

      Distribute By: 在有些情况下,我们需要控制某个特定行应该到哪个 reducer,通常是为 了进行后续的聚集操作。distribute by 类似 MR 中 partition(自定义分区),进行分区,通常结合 sort by 使用。

      1. 按照部门编号分区,再按照员工编号降序排序

        set mapreduce.job.reduces=3; 
        insert overwrite local directory
        '/opt/module/data/distribute-result'
        select * from emp
        distribute by deptno
        sort by empno desc;
        
        • distribute by 的分区规则是根据分区字段的 hash 码与 reduce 的个数进行模除后, 余数相同的分到一个区。
        • Hive 要求 DISTRIBUTE BY 语句要写在 SORT BY 语句之前。

      distribute by 控制 map的输出在reduer中是如何划分的,mapreduce job 中传输的所有数据都是按照键-值对的方式进行组织的,因此hive在将用户的查询语句转换成mapreduce job时,其必须在内部使用这个功能。默认情况下,MapReduce计算框架会依据map输入的键计算相应的哈希值,然后按照得到的哈希值将键-值对均匀分发到多个reducer中去,不过不幸的是,这也是意味着当我们使用sort by 时,不同reducer的输出内容会有明显的重叠,至少对于排序顺序而已只这样,即使每个reducer的输出的数据都有序的。如果我们想让同一年的数据一起处理,那么就可以使用distribute by 来保证具有相同年份的数据分发到同一个reducer中进行处理,然后使用sort by 来安装我们的期望对数据进行排序。

      参考:https://blog.csdn.net/weixin_41122339/article/details/81708373?utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-2.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-2.no_search_link

    4. ClusterBy

      当 distribute by 和 sorts by 字段相同时,可以使用 cluster by 方式。

      但是ClusterBy不能指定排序的升降序

六、分区与分桶表

  1. 分区表

    ​ 分区表实际上就是对应一个 HDFS 文件系统上的独立的文件夹,该文件夹下是该分区所 有的数据文件。Hive 中的分区就是分目录,把一个大的数据集根据业务需要分割成小的数据 集。在查询时通过 WHERE 子句中的表达式选择查询所需要的指定的分区,这样的查询效率 会提高很多。

    1. 创建分区表

      create table dept_partition( 
          deptno int, dname string
          )
      partitioned by (day string)
      row format delimited fields terminated by '\t';
      

      分区字段不能是表中已经存在的数据,可以将分区字段看作是表的伪列

      建表语句partitioned注意加ed,而导入数据则不需要

    2. 加载数据时指定分区

      load data local inpath '/opt/module/hive/datas/dept_20200401.log'
      into table dept_partition partition(day='20200401');
      

      分区表加载数据时,必须指定分区

    3. 增加分区

      • 创建单个或多个分区

        alter table dept_partition add partition(day='20200405') partition(day='20200406');
        
      • 删除单个或多个分区

        alter table dept_partition drop partition (day='20200404'), partition(day='20200405');
        

      注意:增加多个分区时,用空格分隔,删除多个分区时,用逗号分隔

    4. 查看分区表有多少分区

      show partitions table_name

    5. 查看分区表的结构

      desc formatted table_name partition

  2. 二级分区表

    1. 创建二级分区表

      create table dept_partition2( 
          deptno int, dname string, loc string
      )
      partitioned by (day string, hour string)
      
    2. 载入数据

      load data local inpath '/opt/module/hive/datas/dept_20200401.log' into table dept_partition2 partition(day='20200401', hour='12');
      
    3. 将数据直接上传到分区目录上,让分区表和数据产生关联的三种方式

      • 上传数据后修复

        dfs -mkdir -p
        /user/hive/warehouse/mydb.db/dept_partition2/day=20200401/hour=13;
        

        若直接查询是查询不到刚刚导入的数据,需要进行修复

        msck repair table dept_partition2;

        再次查询即可查到数据

      • 上传数据后添加分区

        alter table dept_partition2 add partition(day='201709',hour='14');

      • 创建分区的文件夹后导入数据

        load data local inpath '/opt/module/hive/datas/dept_20200401.log' into table dept_partition2 partition(day='20200401',hour='15');
        

        导入数据时指定分区

  3. 动态分区

    ​ 关系型数据库中,对分区表 Insert 数据时候,数据库自动会根据分区字段的值,将数据 插入到相应的分区中,Hive 中也提供了类似的机制,即动态分区(Dynamic Partition),只不过, 使用 Hive 的动态分区,需要进行相应的配置。

    1. 开启动态分区参数设置(默认开启)

      set hive.exec.dynamic.partition=true;

    2. 设置为非严格模式(动态分区的模式,默认 strict,表示必须指定至少一个分区为 静态分区,nonstrict 模式表示允许所有的分区字段都可以使用动态分区。)

      set hive.exec.dynamic.partition.mode=nonstrict;

    3. 在所有执行 MR 的节点上,最大一共可以创建多少个动态分区。默认 1000

      hive.exec.max.dynamic.partitions=1000

    4. 在每个执行 MR 的节点上,最大可以创建多少个动态分区。该参数需要根据实际 的数据来设定。比如:源数据中包含了一年的数据,即 day 字段有 365 个值,那么该参数就

      需要设置成大于 365,如果使用默认值 100,则会报错。

      hive.exec.max.dynamic.partitions.pernode=100

    5. 整个 MR Job 中,最大可以创建多少个 HDFS 文件。默认 100000

      hive.exec.max.created.files=100000

    6. 当有空分区生成时,是否抛出异常。一般不需要设置。默认 false

      hive.error.on.empty.partition=false

    案例

    需求:将 dept 表中的数据按照地区(loc 字段),插入到目标表 dept_partition 的相应 分区中。

    1. 创建目标分区表

      create table dept_partition_dy(id int, name string) partitioned by (loc int) row format delimited fields terminated by '\t';
      
    2. 设置动态分区

      set hive.exec.dynamic.partition.mode = nonstrict;
      
      insert into table dept_partition_dy partition(loc) select deptno, dname, loc from dept;
      

      插入的数据会根据loc字段自动分区,默认时按照查询字段的最后一个字段进行分区

    3. Hive3.0新特性

      当我们插入数据到动态分区表中,insert into table table_name 后可以不指定partition()分区字段,也可以实现动态分区,即:

      insert into table dept_partition_dy
      select deptno, dname, loc from dept;
      

      会根据最后一个字段当作分区字段进行动态分区

      甚至在严格模式下:

      set hive.exec.dynamic.partition.mode = strict;
      
      insert into table dept_partition_dy partition(loc)
      select deptno, dname, loc from dept; //语句报错
      
      insert into table dept_partition_dy
      select deptno, dname, loc from dept; //语句不报错
      

      相当于Hive底层又将严格模式改为非严格模式再进行动态分区

    动态分区功能默认情况下是没有开启的,默认是以 strict 模式执行,这种模式下要求至少有一列分区字段是静态的。这样做的好处是可以防止因设计或其它错误查询导致产生大量的分区,比如不小心使用了时间戳作为分区字段,那将是灾难。在日常导入一天的数据,通常是指定日期为静态分区,小时为动态分区,进行混合模式导入。

  4. 分桶表

    ​ 分区提供一个隔离数据和优化查询的便利方式。不过,并非所有的数据集都可形成合理 的分区。对于一张表或者分区,Hive 可以进一步组织成桶,也就是更为细粒度的数据范围 划分。

    ​ 分桶是将数据集分解成更容易管理的若干部分的另一个技术。 分区针对的是数据的存储路径;分桶针对的是数据文件。

    1. 创建分桶表

      create table stu_buck(id int, name string) clustered by(id)
      into 4 buckets
      row format delimited fields terminated by '\t';
      
    2. 导入数据

      load data inpath '/student.txt' into table stu_buck;

      导入的数据会在文件目录下生成与桶数量相等的文件数,各个文件中存放数据的一部分

    3. 分桶规则

      ​ Hive 的分桶采用对分桶字段的值进行哈希,然后除以桶的个数求余的方 式决定该条记录存放在哪个桶当中

    4. 注意事项

      • reduce 的个数设置为-1,让 Job 自行决定需要用多少个 reduce 或者将 reduce 的个 数设置为大于等于分桶表的桶数
      • 从 hdfs 中 load 数据到分桶表中,避免本地文件找不到问题
      • 不要使用本地模式
    5. 插入方式将数据导入分桶表

      insert into table stu_buck select * from student_insert;

    6. 抽样查询

      ​ 对于非常大的数据集,有时用户需要使用的是一个具有代表性的查询结果而不是全部结 果。Hive 可以通过对表进行抽样来满足这个需求。

      语法:TABLESAMPLE(BUCKET x OUT OF y)

      • 随机抽样基于整行数据

      select * from stu_buck tablesample(bucket 1 out of 4 on id);

      • 随机抽样基于指定列(使用分桶列更高效)

      select * from stu_buck tablesample(bucket 1 out of 4 in id);

      • 随机抽样基于block size
         select * from bucket1 tablesmple(10 percent)s;(随机抽取桶表的百分之10的数据)
         select * from bucket1 tablesmple(1m)s;(随机抽取1m的桶表数据)
         select * from bucket1 tablesmple(10 rows)s;(随机抽取桶表的10行数据)
      

      例如,table总共分了4份,当y=2时,抽取(4/2=)2个bucket的数据,当y=8时,抽取bucket的1/2数据。

      x表示从哪个bucket开始抽取,如果需要取多个分区,以后的分区号为当前分区号加上y。

      例如,table总bucket数为4,tablesample(bucket 1 out of 2),表示总共抽取(4/2=)2个bucket的数据,抽取第1(x)个和第3(x+y)个bucket的数据。

      注意:x的值必须小于等于y的值。否则:

      Numerator should not be bigger than denominator in sample clause for table stu_buck
      

七、函数

  1. 常用内置函数

    • NVL:给值为 NULL 的数据赋值,它的格式是 NVL( value,default_value)。它的功能是如 果 value 为 NULL,则 NVL 函数返回 default_value 的值,否则返回 value 的值,如果两个参数 都为 NULL ,则返回 NULL。

    • CASE WHEN THEN ELSE END

      select
       dept_id,
       sum(case sex when '男' then 1 else 0 end) male_count,
       sum(case sex when '女' then 1 else 0 end) female_count
      from emp_sex
      group by dept_id;
      
  2. 行转列

    • CONCAT(string A/col, string B/col…):返回输入字符串连接后的结果,支持任意个输入字符串

    • CONCAT_WS(separator, str1, str2,...):它是一个特殊形式的 CONCAT()。第一个参数剩余参 数间的分隔符。分隔符可以是与剩余参数一样的字符串。如果分隔符是 NULL,返回值也将 为 NULL。这个函数会跳过分隔符参数后的任何 NULL 和空字符串。分隔符将被加到被连接 的字符串之间

      注意: CONCAT_WS must be "string or array

    • COLLECT_SET(col):函数只接受基本数据类型,它的主要作用是将某字段的值进行去重 汇总,产生 Array 类型字段。

  3. 列转行

    EXPLODE(col):将 hive 一列中复杂的 Array 或者 Map 结构拆分成多行。

    LATERAL VIEW

    用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias

    解释:用于和 split, explode 等 UDTF 一起使用,它能够将一列数据拆成多行数据,在此 基础上可以对拆分后的数据进行聚合。

  4. 窗口函数

    • OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。

      CURRENT ROW:当前行

      n PRECEDING:往前 n 行数据

      n FOLLOWING:往后 n 行数据 UNBOUNDED:起点,

      UNBOUNDED PRECEDING 表示从前面的起点,

      UNBOUNDED FOLLOWING 表示到后面的终点 LAG(col,n,default_val):往前第 n 行数据 LEAD(col,n, default_val):往后第 n 行数据

      NTILE(n):把有序窗口的行分发到指定数据的组中,各个组有编号,编号从 1 开始,对 于每一行,NTILE 返回此行所属的组的编号。注意:n 必须为 int 类型。

    案例:

    select name,orderdate,cost,
    sum(cost) over() as sample1,--所有行相加
    sum(cost) over(partition by name) as sample2,--按 name 分组,组内数据相加 sum(cost) over(partition by name order by orderdate) as sample3,--按 name 分组,组内数据累加
    sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and current row ) as sample4 ,--和 sample3 一样,由起点到 当前行的聚合
    sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and current row) as sample5, --当前行和前面一行做聚合 sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING AND 1 FOLLOWING ) as sample6,--当前行和前边一行及后面一行
    sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING ) as sample7 --当前行及后面所有行
    from business;
    

    ​ rows 必须跟在 order by 子句之后,对排序的结果进行限制,使用固定的行数来限制分 区中的数据行数量

  5. Rank()

    RANK() :排序相同时会重复,总数不会变

    DENSE_RANK() :排序相同时会重复,总数会减少

    ROW_NUMBER(): 会根据顺序计算

    rank()要结合窗口函数进行使用

    案例:

    select name,
    subject, score,
    rank() over(partition by subject order by score desc) rp, dense_rank() over(partition by subject order by score desc) drp, row_number() over(partition by subject order by score desc) rmp
    from score;
    
    //结果如下
    name subject score rp drp rmp
    Jack 数学 95 1 1 1
    Mark 数学 86 2 2 2
    Duck 数学 85 3 3 3
    Lux 数学 56 4 4 4
    Lux 英语 84 1 1 1
    Duck 英语 84 1 1 2
    Mark 英语 78 3 2 3
    Jack 英语 68 4 3 4
    Duck 语文 94 1 1 1
    Mark 语文 87 2 2 2
    Lux 语文 65 3 3 3
    Jack 语文 64 4 4 4
    

    求各个科目前三名:

    将上表作为子表,加入过滤条件 rp <=3即可
    
  6. 自定义函数

    1. 根据用户自定义函数类别分为以下三种:

      • UDF(User-Defined-Function)

        一进一出

      • UDAF(User-Defined Aggregation Function)

        聚集函数,多进一出

        类似于:count/max/min

      • UDTF(User-Defined Table-Generating Functions)

        一进多出

        如 lateral view explode()

    2. 编程步骤

      • 继承 Hive 提供的类

        org.apache.hadoop.hive.ql.udf.generic.GenericUDF

        org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;

      • 实现类中的抽象方法

      • 在hive的命令行窗口创建函数

        添加jar

        add jar linux_jar_path

        创建function

        create [temporary] function [dbname.]function_name AS class_name;

        删除函数

        drop [temporary] function [if exists] [dbname.]function_name;

    3. 实操

      1. 导入依赖

        <dependencies>
            <dependency>
                <groupId>org.apache.hive</groupId>
                <artifactId>hive-exec</artifactId>
                <version>3.1.2</version>
            </dependency>
        </dependencies>
        
      2. 创建一个类

        import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
        import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
        import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
        import org.apache.hadoop.hive.ql.metadata.HiveException;
        import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
        import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
        import 
        org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectIn
        spectorFactory;
        /**
        * 自定义 UDF 函数,需要继承 GenericUDF 类
        * 需求: 计算指定字符串的长度
        */
        public class MyStringLength extends GenericUDF {
             /**
             *
             * @param arguments 输入参数类型的鉴别器对象
             * @return 返回值类型的鉴别器对象
             * @throws UDFArgumentException
             */
             @Override
             public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
                 // 判断输入参数的个数
                 if(arguments.length !=1){
                    throw new UDFArgumentLengthException("Input Args Length Error!!!");
                 }
                 // 判断输入参数的类型
        
                if(!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)){
                    throw new UDFArgumentTypeException(0,"Input Args Type Error!!!");
                }
                //函数本身返回值为 int,需要返回 int 类型的鉴别器对象
                return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
             }
            
             /**
             * 函数的逻辑处理
             * @param arguments 输入的参数
             * @return 返回值
             * @throws HiveException
             */
             @Override
             public Object evaluate(DeferredObject[] arguments) throws HiveException {
                 if(arguments[0].get() == null){
                    return 0;
                 }
                 return arguments[0].get().toString().length();
        	}
        
            @Override
            public String getDisplayString(String[] children) {
                 return "";
        	} 
        }
        
      3. 打包好之后上传到服务器

      4. 添加jar包到hive的classpath

        add jar /opt/module/data/myudf.jar;

        也可以把jar包放到hive目录的lib下,启动hive时将自动扫描lib

      5. 创建临时函数和开发好的java class关联

        create temporary function my_len as "com.tomato.hive.MyStringLength";

        写的是jar包中的全类名

      6. 使用函数

        select ename,my_len(ename) ename_len from emp;

    4. 自定义UDTF函数

      代码实现:

      import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
      import org.apache.hadoop.hive.ql.metadata.HiveException;
      import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
      import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
      import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
      import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
      import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
      import java.util.ArrayList;
      import java.util.List;
      public class MyUDTF extends GenericUDTF {
         private ArrayList<String> outList = new ArrayList<>();
         @Override
         public StructObjectInspector initialize(StructObjectInspector argOIs) 
             throws UDFArgumentException {
             //1.定义输出数据的列名和类型
             List<String> fieldNames = new ArrayList<>();
             List<ObjectInspector> fieldOIs = new ArrayList<>();
             //2.添加输出数据的列名和类型
             fieldNames.add("lineToWord");
             
             fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
             return 
             ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, 
              fieldOIs);
         }
         @Override
         public void process(Object[] args) throws HiveException {
             //1.获取原始数据
             String arg = args[0].toString();
             //2.获取数据传入的第二个参数,此处为分隔符
             String splitKey = args[1].toString();
             //3.将原始数据按照传入的分隔符进行切分
             String[] fields = arg.split(splitKey);
             //4.遍历切分后的结果,并写出
             for (String field : fields) {
                 //集合为复用的,首先清空集合
                 outList.clear();
                 //将每一个单词添加至集合
                 outList.add(field);
                 //将集合内容写出
                 forward(outList);
             }
         }
         @Override
         public void close() throws HiveException {
         } 
      }
      

      然后与自定义UDF函数同理

      使用:

      select myudtf("hello,world,hadoop,hive",",");

八、压缩与储存

  1. Hadoop压缩配置

    • MR支持的压缩编码
    压缩格式 算法 文件扩展名 是否可切分
    DEFLATE DEFLATE .deflate
    Gzip DEFLATE .gz
    bzip2 bzip2 .bz2
    LZO LZO .lzo
    Snappy Snappy .snappy

    为了支持多种压缩、解压缩算法,Hadoop引入了编码/解码器,如下:

    压缩格式 对应的编码/解码器
    DEFLATE org.apache.hadoop.io.compress.DefaultCodec
    gzip org.apache.hadoop.io.compress.GzipCodec
    bzip2 org.apache.hadoop.io.compress.BZip2Codec
    LZO com.hadoop.compression.lzo.LzopCodec
    Snappy org.apache.hadoop.io.compress.SnappyCodec

    压缩性能的比较:

    压缩算法 原始文件大小 压缩文件大小 压缩速度 解压速度
    gzip 8.3GB 1.8GB 17.5MB/s 58MB/s
    bzip2 8.3GB 1.1GB 2.4MB/s 9.5MB/s
    LZO 8.3GB 2.9GB 49.3MB/s 74.6MB/s
    • 压缩参数配置
    参数 默认值 阶段 建议
    io.compression.codecs(在 core-site.xml 中配置) org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec, org.apache.hadoop.io.compress.Lz4Codec 输入压缩 Hadoop 使用文件扩展 名判断是否支持某种 编解码器
    mapreduce.map.output.com press false mapper 输出 这个参数设为 true 启 用压缩
    mapreduce.map.output.com press.codec org.apache.hadoop.io.compress.DefaultCodec mapper 输出 使用 LZO、LZ4 或 snappy 编解码器在此 阶段压缩数据
    mapreduce.output.fileoutput format.compress false reducer 输出 这个参数设为 true 启 用压缩
    mapreduce.output.fileoutput format.compress.codec org.apache.hadoop.io.compress. DefaultCodec reducer 输出 使用标准工具或者编 解码器,如 gzip 和 bzip2
    mapreduce.output.fileoutput format.compress.type RECORD reducer 输出 SequenceFile 输出使用 的压缩类型:NONE 和 BLOCK
  2. 开启Map输出阶段压缩(MR引擎)

    开启后可以减少job中map和reduce task间数据传输量

    三个步骤

    1. 开启 hive 中间传输数据压缩功能

      set hive.exec.compress.intermediate=true;

    2. 开启 mapreduce 中 map 输出压缩功能

      set mapreduce.map.output.compress=true;

    3. 设置 mapreduce 中 map 输出数据的压缩方式

      set mapreduce.map.output.compress.codec= org.apache.hadoop.io.compress.SnappyCodec;

    4. 执行语句

  3. 开启Reduce输出阶段压缩

    ​ 当 Hive 将 输 出 写 入 到 表 中 时 , 输出内容同样可以进行压缩。属性hive.exec.compress.output 控制着这个功能。用户可能需要保持默认设置文件中的默认值 false, 这样默认的输出就是非压缩的纯文本文件了。用户可以通过在查询语句或执行脚本中设置这个值为 true,来开启输出结果压缩功能。

    四个步骤

    1. 开启 hive 最终输出数据压缩功能

      set hive.exec.compress.output=true;

    2. 开启 mapreduce 最终输出数据压缩

      set mapreduce.output.fileoutputformat.compress=true;

    3. 设置 mapreduce 最终数据输出压缩方式

      set mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.SnappyCodec;

    4. 设置 mapreduce 最终数据输出压缩为块压缩

      set mapreduce.output.fileoutputformat.compress.type=BLOCK;

    5. 用insert导出结果

      可以发现文件大小比压缩前要小

  4. 文件存储格式

    Hive 支持的存储数据的格式主要有:TEXTFILE 、SEQUENCEFILE、ORC、PARQUET。

    1. 列式存储和行式存储
      Hive个人学习记录_字段_02

    ​ 从上图可以很清楚地看到,行式存储下一张表的数据都是放在一起的,但列式存储下都被分开保存了。所以它们就有了如下这些优缺点:

    行式存储 列式存储
    优点 数据被保存在一起INSERT/UPDATE容易 查询时只有涉及到的列会被读取
    投影(projection)很高效
    任何列都能作为索引
    缺点 选择(Selection)时即使只涉及某几列,所有数据也都会被读取 选择完成时,被选择的列要重新组装
    INSERT/UPDATE比较麻烦
    • 行存储的特点:查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度 更快。

    • 列存储的特点:因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算 法。

      TEXTFILE 和 SEQUENCEFILE 的存储格式都是基于行存储的;

      ORC 和 PARQUET 是基于列式存储的。

    1. TextFile 格式

      ​ 默认格式,数据不做压缩,磁盘开销大,数据解析开销大。可结合 Gzip、Bzip2 使用, 但使用 Gzip 这种方式,hive 不会对数据进行切分,从而无法对数据进行并行操作。

    2. Orc 格式

      ​ Orc (Optimized Row Columnar)是 Hive 0.11 版里引入的新的存储格式。 如下图所示可以看到每个 Orc 文件由 1 个或多个 stripe 组成,每个 stripe 一般为 HDFS的块大小,每一个 stripe 包含多条记录,这些记录按照列进行独立存储,对应到 Parquet 中的 row group 的概念。每个 Stripe 里有三部分组成,分别是 Index Data,Row Data,Stripe Footer:

      Hive个人学习记录_hadoop_03

      • Index Data:一个轻量级的 index,默认是每隔 1W 行做一个索引。这里做的索引应该 只是记录某行的各字段在 Row Data 中的 offset。
      • Row Data:存的是具体的数据,先取部分行,然后对这些行按列进行存储。对每个 列进行了编码,分成多个 Stream 来存储。
      • Stripe Footer:存的是各个 Stream 的类型,长度等信息。

      ​ 每个文件有一个 File Footer,这里面存的是每个 Stripe 的行数,每个 Column 的数据类 型信息等;每个文件的尾部是一个 PostScript,这里面记录了整个文件的压缩类型以及 FileFooter 的长度信息等。在读取文件时,会 seek 到文件尾部读 PostScript,从里面解析到 File Footer 长度,再读 FileFooter,从里面解析到各个 Stripe 信息,再读各个 Stripe,即从后 往前读。

    3. Parquet 格式

      ​ Parquet 文件是以二进制方式存储的,所以是不可以直接读取的,文件中包括该文件的 数据和元数据,因此 Parquet 格式文件是自解析的。

      • 行组(Row Group):每一个行组包含一定的行数,在一个 HDFS 文件中至少存储一 个行组,类似于 orc 的 stripe 的概念。
      • 列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连 续的存储在这个行组文件中。一个列块中的值都是相同类型的,不同的列块可能使用不同的 算法进行压缩。
      • 页(Page):每一个列块划分为多个页,一个页是最小的编码的单位,在同一个列块 的不同页可能使用不同的编码方式。

      ​ 通常情况下,在存储 Parquet 数据的时候会按照 Block 大小设置行组的大小,由于一般 情况下每一个 Mapper 任务处理数据的最小单位是一个 Block,这样可以把每一个行组由一 个 Mapper 任务处理,增大任务执行并行度。Parquet 文件的格式。

    存储文件的查询速度总结:查询速度相近

  5. 存储和压缩结合

    创建两个ORC存储方式的表,压缩方式分别为ZLIB(默认),SNAPPY。再创建一个parquet存储方式,压缩方式为SNAPPY的表

    create table log_orc_snappy( 
        track_time string,
        url string, 
        referer string, ip string,
        end_user_id string, city_id string
    )
    row format delimited fields terminated by '\t'
    stored as orc
    tblproperties("orc.compress"="SNAPPY"); 
    

    得到结论:ZLIB 比 Snappy 压缩的还小。原因是 ZLIB 采用的是 deflate 压缩算法。比 snappy 压缩的 压缩率高。

    create table log_parquet_snappy(
        track_time string,
    	url string,
        session_id string,
        referer string,
    	ip string,
        end_user_id string,
        city_id string
    )
    row format delimited fields terminated by '\t'
    stored as parquet 
    

    得出结论:压缩效率比ZLIB和Snappy小

  6. 总结:

    ​ 在实际的项目开发当中,hive 表的数据存储格式一般选择:orc 或 parquet。压缩方式一 般选择 snappy,lzo。