# 三、Hive 内部表

内部表，也称为本机表或托管表，由 Hive 控制——事实上，Hive 拥有这些表的存储。它们在 HDFS 仍在继续，这意味着您可以获得可靠、广泛可用的数据的所有好处，并且，如果您选择通用格式，您仍然可以使用其他 Hadoop 工具查询 Hive 表文件。

当您使用内部表时，您将获得更多 Hive 功能的主要好处。目前，只有托管表才有可能更新或删除数据(我们将在第 7 章的 DDL 和 Hive 中的 DML 中详细介绍这一点)，HiveQL 有许多边缘情况只适用于内部表。

使用内部表可以让您专注于以您想要的方式建模数据，而 Hive 则担心数据是如何物理维护的。对于大型数据集，您可以配置分片，以便在物理上将表拆分到不同的文件中，从而提高性能。

Hive 还允许并管理临时表，这些表对于存储会话结束时 Hive 销毁的中间结果集非常有用。Hive 的完整提取、转换和加载(ETL)工具可用于内部表，这意味着它们是存储主要通过 Hive 访问的新数据的好选择。

Hive 在 HDFS 将内部表存储为文件，这将允许您使用其他 Hadoop 工具访问它们。整个 Hadoop 生态系统不支持更优化的存储选项。即使您正在使用一系列工具，也可以使用内部 Hive 表，但是您需要选择一种可互操作的格式。

create table 语句将创建一个内部表，除非您指定外部修饰符(我们将在第 4 章 HDFS 外部表和第 5 章 HBase 外部表中介绍)。最简单的语句，如代码清单 11 所示，将使用所有默认值创建一个只有一列的表。

 11:创建内部配置单元表

```
  create table dual(r string);

```

hive 表在 HDFS 的默认根位置是/user/hive/warehouse，在代码清单 12 中我们可以看到，当 create table 语句运行时，Hive 创建了一个名为 dual 的目录，但是该目录将是空的。

 12:蜂巢创建的 HDFS 文件夹

```
  root@hive:/hive-setup# hdfs dfs
  -ls /user/hive/warehouse/    
  Found 1 item
  drwxrwxr-x   - root root       4096
  2016-01-25 18:02 /user/hive/warehouse/dual
  root@hive:/hive-setup# hdfs dfs
  -ls /user/hive/warehouse/dual
  root@hive:/hive-setup#

```

当我们向新表中插入数据时，Hive 将在 HDFS 创建一个文件并填充它。代码清单 13 显示了新表中的一个 insert 语句，以及 Beeline 的所有输出，这让我们可以看到 Hive 正在做什么。

 13:在配置单元表中插入一行

```
  > insert into dual(r)
  values('1');
  INFO  :
  Number of reduce tasks is set to 0 since there's no reduce operator
  INFO  :
  number of splits:1
  INFO  :
  Submitting tokens for job: job_local1569518498_0001
  INFO  : The
  url to track the job: http://localhost:8080/
  INFO  : Job
  running in-process (local Hadoop)
  INFO  :
  2016-01-25 18:07:39,487 Stage-1 map = 100%,  reduce = 0%
  INFO  : Ended
  Job = job_local1569518498_0001
  INFO  :
  Stage-4 is selected by condition resolver.
  INFO  :
  Stage-3 is filtered out by condition resolver.
  INFO  :
  Stage-5 is filtered out by condition resolver.
  INFO  :
  Moving data to: file:/user/hive/warehouse/dual/.hive-staging_hive_2016-01-25_18-07-37_949_178012634824589876-2/-ext-10000
  from
  file:/user/hive/warehouse/dual/.hive-staging_hive_2016-01-25_18-07-37_949_178012634824589876-2/-ext-10002
  INFO  :
  Loading data to table default.dual from file:/user/hive/warehouse/dual/.hive-staging_hive_2016-01-25_18-07-37_949_178012634824589876-2/-ext-10000
  INFO  : Table
  default.dual stats: [numFiles=1, numRows=1, totalSize=2, rawDataSize=1]
  No rows
  affected (1.724 seconds)

```

INFO 级别的输出有很多细节，其中一些提供了有用的信息:

*   Hive 的 Web UI 中跟踪作业的 URL 是什么(适合长时间运行的查询)。
*   作业是如何运行的(进行中而不是通过纱)。
*   工作是如何组织的(分成地图和简化阶段)。
*   Hive 如何处理数据(首先将其加载到暂存文件中)。
*   查询返回了多少行。“没有受影响的行”响应并不意味着没有插入行，而是 Beeline 中的计数器回答了查询实际返回了多少行。

Hive 通过暂存文件将数据追加到 HDFS。Hive 将所有新数据写入其中，当提交写入时，Hive 将文件移动到 HDFS 的正确位置。在内部表中插入行是一个 ACID 操作，当它完成时，我们可以使用代码清单 14 中的列表和 cat 命令在 HDFS 查看文件及其内容。

 14:查看配置单元表的文件内容

```
  root@hive:/hive-setup# hdfs dfs
  -ls /user/hive/warehouse/dual/
  Found 1 items
  -rwxrwxr-x   1 root root          2
  2016-01-26 07:00 /user/hive/warehouse/dual/000000_0
  root@hive:/hive-setup# hdfs dfs
  -cat /user/hive/warehouse/dual/000000_0 
  1

```

hdfs dfs -ls 命令告诉我们，双表的目录中没有一个名为 000000_0 的文件，该文件的内容由一个字符组成一行，即我们插入到表中的“1”。我们可以读取文件的内容，因为默认格式是文本，但是 Hive 也支持其他更高效的文件格式。

create table 命令支持存储为子句，该子句指定数据文件的物理文件格式。该子句是可选的，如果您像我们在代码清单 14 中所做的那样省略它，Hive 将默认存储为 textfile。

Hive 对 Hadoop 生态系统中其他工具支持的其他文件类型具有本机支持。以下是三个最受欢迎的:

*   AVRO—基于模式的二进制格式，可跨许多平台互操作。
*   ORC—优化的行列格式，作为一种高效的格式为 Hive 构建。
*   PARQUET——一种压缩的柱状格式，在 Hadoop 中广泛使用。

如果您使用 Hive 创建新数据，ORC 格式通常是存储大小和访问性能的最佳选择，但它并没有得到其他 Hadoop 工具的广泛支持。如果要使用其他工具访问相同的数据(如 Pig 和 Spark)，更常用的是 Parquet 和 Avro。

柱状文件格式比平面文本文件具有更智能的结构，它们通常以块的形式存储数据，并带有一个轻量级索引，Hive 使用该索引来定位它需要读取的确切块。读取操作不需要扫描整个文件，只需要读取索引和包含数据的块。

默认情况下，Avro、ORC 和 Parquet 都提供压缩。对于大数据量，压缩和解压缩数据所需的 CPU 时间开销与通过网络将较小的文件或块从磁盘传输到内存所节省的时间相比通常可以忽略不计。

| ![](img/00005.gif) | 注意:您可以使用 alter table… set fileformat 更改现有表的文件格式，但是 Hive 不会自动转换所有现有数据。如果您在表中有数据，并且为了写入更多数据而更改了文件格式，那么您将有多个不同格式的文件—Hive 将无法从表中读取数据。如果你想改变格式，你应该使用第 6 章中描述的一个 ETL 选项。 |

Hive 最吸引人的特性之一是它能够将结构应用于非结构化或半结构化的 Hadoop 数据。在 Hive 中定义表时，每一列都使用特定的数据类型。您不需要担心数据如何与内部表映射，因为 Hive 拥有存储，并负责序列化和反序列化磁盘上的数据。

Hive 提供了典型数据库中使用的所有基本数据类型，它还包括一些更高值的数据类型，允许您更准确地建模数据。对于复杂的数据类型，Hive 使用的列可以在不同类型的集合中包含多个值。我们将在映射现有数据的上下文中查看第 4 章 HDFS 外部表中的内容。

Hive 内置了对数、平方根和模数等数学运算函数，比许多关系数据库提供了更丰富的数值数据支持。Hive 还可以在多个整数和浮点类型之间隐式转换(只要您从较小的容量转换到较大的容量)。

按照容量的升序，以下是四种整数类型:

*   TINYINT 从-128 到+127，在文字中用“Y”做后缀。
*   SMALINT—从-32768 到+32767，在文字中带有“S”的后缀。
*   INT—从-2147483648 到+2147483647，不需要后缀。
*   BIGINT—从-9223372036854775808 到+9223372036854775807，后缀‘L’。

INT 是默认的整数类型，但是如果您存储的是序列、计数或 UNIX 时间戳，则+/-2Bn 的范围会受到限制(尽管 Hive 对此有特定的数据类型)。

如果超过整数字段的容量，Hive 不会产生运行时错误。相反，它包装了值(从正到负，反之亦然)，所以要小心，您的计算不会因为无声地破坏列容量而扭曲。

代码清单 15 显示了突破列容量的结果——在这种情况下，将一个正的 TINYINT 变成负值，将一个负的 SMALLINT 变成正值。

 15:从正数到负数换行

```
  > select (127Y + 1Y),
  (-32759S - 10S);
  +-------+--------+--+
  |  _c0  | 
  _c1   |
  +-------+--------+--+
  | -128  |
  32767  |
  +-------+--------+--+

```

浮点类型允许更高的精度和更大的数字范围:

*   浮点—单精度、4 字节容量。
*   双精度，8 字节容量。
*   十进制—可变精度，38 位容量。

DECIMAL 类型默认精度为 10，小数位数为零，但它们通常用特定的值来定义，例如，一个有两个小数位的五位数字段将被定义为 DECIMAL (5，2)，最大值为 999.99。

使用零刻度的 DECIMAL 类型，可以存储比 BIGINT 更大的整数。表示为十进制的整数的后缀是 BigDecimal 类型基于 Java 的 BigDecimal 类型)，如代码清单 16 所示，这允许您超越 BIGINT 限制。

 16:用大十进制表示大整数

```
  >
  select (9223372036854775807L * 10), (9223372036854775807BD * 10);
  +------+-----------------------+--+
  | _c0 
  |          _c1          |
  +------+-----------------------+--+
  | -10  |
  92233720368547758070  |
  +------+-----------------------+--+

```

Hive 支持浮点数的科学表示法和标准表示法，它允许在相同的行和表中混合使用它们。

当您达到 Hive 可以存储的小数类型的最小和最大限制时，hive 将接近零或无穷大。但是这些极限大约是+/-308 的幂，这意味着你不太可能达到它们。代码清单 17 显示了如果您这样做了会发生什么。

 17:突破浮点数限制

```
  >
  select 1E308, 1E330, -1E-308, -1E-330;
  +----------+-----------+------------+-------+--+
  |   _c0   
  |    _c1    |    _c2     |  _c3  |
  +----------+-----------+------------+-------+--+
  | 1.0E308  |
  Infinity  | -1.0E-308  | -0.0  |
  +----------+-----------+------------+-------+--+

```

Hive 中的主要字符类型是 STRING，它不强加最大长度，实际上支持任何大小的字符串。字符类型上的用户定义函数通常使用字符串，但是有多种类型可供选择:

*   字符串—大小不限，文字可以用单引号或双引号分隔。
*   VARCHAR—指定的最大大小，保留输入中的空白。
*   CHAR—固定长度、较小的输入用空白填充。

您可以比较不同字符类型的列中的字符串，但是您必须知道空白是否会影响比较。STRING 和 VARCHAR 类型保留空白，这意味着具有相同文本内容但以不同空格数结尾的值不相等。CHAR 字段总是用空格填充到设定的长度，这样只比较文本。

代码清单 18 创建了一个包含三个字符列的表，并在每个字段中插入包含相同文本的行，并在后面插入不同数量的空格。

 18:创建带有字符字段的表格

```
   >
   create table strings(a_string string, a_varchar varchar(10), a_char
   char(10));
   No
   rows affected (0.192 seconds)
   >
   insert into strings(a_string, a_varchar, a_char) values('a', 'a', 'a');
   No
   rows affected (1.812 seconds)
   >
   insert into strings(a_string, a_varchar, a_char) values('b        ',
   'b     ', 'b');
   No
   rows affected (1.381 seconds)

```

因为表是默认的文件格式，所以它被存储为文本，当我们读取文件时，我们可以看到空白被保存在哪里，如代码清单 19 所示(其中' # '代表默认的分隔符\0001)。

 19:字符字段中空白的存储

```
  root@hive:/hive-setup#
  hdfs dfs -cat /user/hive/warehouse/strings/*              
  a#a#a               

  b       
  #b     #b

```

如果我们查询该表来查找具有匹配列的行，我们将只收到第一行，因为第二行中的字段在尾部空白中有差异。但是如果我们使用 trim()函数来清除空白进行比较，就会返回两行——如代码清单 20 所示。

 20:比较字符字段中的值

```
   >
   select * from strings where a_string = a_varchar and a_string = a_char;
  +-------------------+--------------------+-----------------+--+
  |
   strings.a_string  | strings.a_varchar  | strings.a_char  |
  +-------------------+--------------------+-----------------+--+
  |
   a                 | a                  | a               |
  +-------------------+--------------------+-----------------+--+
  1 row
   selected (0.088 seconds)
   >
   select * from strings where trim(a_string) = trim(a_varchar) and
   trim(a_string) = a_char;
  +-------------------+--------------------+-----------------+--+
  |
   strings.a_string  | strings.a_varchar  | strings.a_char  |
  +-------------------+--------------------+-----------------+--+
  |
   a                 | a                  | a               |
  |
   b                 | b                  | b               |
  +-------------------+--------------------+-----------------+--+
  2 rows
   selected (0.095 seconds)

```

Hive 明确支持日期和时间数据，其类型能够存储高精度时间戳或不带时间组件的日期:

*   时间戳—UNIX 风格的时间戳，记录自纪元以来经过的时间。精度从秒到纳秒不等。
*   日期—没有时间成分的日期。

这两种类型都支持字符串形式的文字表达式，格式为“yyyy-MM-dd”(用于日期)和“yyyy-MM-dd HH:mm:ss.fff”(用于时间戳，最多九个小数位支持纳秒精度)。

如果您有记录为整数 UNIX 时间戳的值，则可以使用 from_unixtime()函数将它们插入时间戳列。请注意，这里只支持第二精度，并且不能在 insert … values 语句中使用函数，这意味着字符串和整数时间戳插入的语法不同，如代码清单 21 所示。

 21:转换日期和时间戳

```
   >
   create table datetimes(a_timestamp timestamp, a_date date);
   No
   rows affected (0.068 seconds)
   >
   insert into datetimes(a_timestamp, a_date) values('2016-01-27
   07:19:01.001', '2016-01-27');
   ...
   No
   rows affected (1.387 seconds)
   >
   from dual insert into table datetimes select from_unixtime(1453562878),
   '2016-01-23';
   ...
   No
   rows affected (1.296 seconds)

```

在这里，我们创建了一个带有时间戳和日期列的表，并在时间戳中插入了两个精度级别不同的行。from dual 是一个技巧，它允许我们使用带有函数的 select 语句作为 insert 的源子句(我们将在第 6 章使用 Hive 的 ETL 中详细介绍)。

Hive 支持时间戳和日期之间的转换，内置的日期函数适用于这两种类型的列。使用时间戳，如果转换为日期类型，时间部分将丢失，使用日期，任何基于时间的函数都将返回空值。代码清单 22 显示了这些转换和一些示例函数。

 22:使用日期类型

```
   >
   select a_timestamp, year(a_timestamp), a_date, year(a_date) from
   datetimes;
  +--------------------------+-------+-------------+-------+--+
  |      
   a_timestamp        |  _c1  |   a_date    |  _c3  |
  +--------------------------+-------+-------------+-------+--+
  | 2016-01-27
   07:19:01.001  | 2016  | 2016-01-27  | 2016  |
  | 2016-01-23
   15:27:58.0    | 2016  | 2016-01-23  | 2016  |
  +--------------------------+-------+-------------+-------+--+
  2 rows
   selected (0.067 seconds)
   >
   select a_timestamp, hour(a_timestamp), a_date, hour(a_date) from
   datetimes;
  +--------------------------+-------+-------------+-------+--+
  |      
   a_timestamp        |  _c1  |   a_date    |  _c3  |
  +--------------------------+-------+-------------+-------+--+
  | 2016-01-27
   07:19:01.001  | 7     | 2016-01-27  | NULL  |
  | 2016-01-23
   15:27:58.0    | 15    | 2016-01-23  | NULL  |
  +--------------------------+-------+-------------+-------+--+
  2 rows
   selected (0.073 seconds)
   >
   select cast(a_timestamp as date), cast(a_date as timestamp) from
   datetimes;
  +--------------+------------------------+--+
  |
   a_timestamp  |         a_date         |
  +--------------+------------------------+--+
  |
   2016-01-27   | 2016-01-27 00:00:00.0  |
  |
   2016-01-23   | 2016-01-23 00:00:00.0  |
  +--------------+------------------------+--+
  2 rows
   selected (0.067 seconds)

```

Hive 中还有另外两种简单类型:

*   BOOLEAN—对于真/假值。
*   二进制—对于任意字节数组，Hive 不解释。

布尔值用文字 true 和 false 表示；您可以将其他类型转换为布尔值，但可能不会得到预期的结果。与其他语言不同，在 Hive 中，false 表示为文字 false 或数值 0—任何其他值(如数字 1 或-1；或字符串“真”或“假”)表示真。

二进制值可用于存储任何字节数组，但数据类型的行为不同于 SQL 数据库中的 blob 数据类型。Hive 中的二进制列值与行的其余部分一起存储在数据文件中，而不是作为指向 blob 的指针。因为 Hive 不与二进制数据交互，所以二进制值没有被广泛使用。

在这一章中，我们已经讨论了如何在 Hive 中使用内部表。内部表的底层数据作为文件存储在 HDFS，这意味着 Hive 可以免费获得 Hadoop 的所有可靠性和可扩展性。通过使用内部表，Hive 在文件级别控制读写，因此 Hive 的完整功能集是可用的。

我们还研究了 Hive 提供的不同文件格式，文本文件作为默认的更高效的列格式，例如 ORC 和 Parquet，它们也是本地支持的。使用内部表时选择的格式将取决于是否有任何其他系统需要访问原始数据。如果没有，Hive 的 ORC 格式是不错的选择；否则在 Hadoop 中很好地支持了 Parquet 和 Avro。许多选项都支持平面文件。

最后，我们查看了 Hive 中所有可用的简单数据类型，注意到这些类型同样适用于内部和外部表(前提是它们可以从源数据正确映射)。在接下来的章节中，我们将研究如何使用外部表，以及如何将 Hive 用于现有的 HDFS 文件和 HBase 表。