【Hive】04-HiveQL:数据定义

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenchaohao12321/article/details/82560449

HiveQL是Hive查询语言。和普遍使用的所有SQL方言一样,它不完全遵守任一种ANSI SQL标准的修订版。HiveQL可能和MySQL的方言最接近,但是两者还是存在显著性差异的。Hive不支持行级插人操作、更新操作和删除操作。Hive也不支持事务。Hive增加了在Hadoop背景下的可以提供更高性能的扩展,以及一些个性化的扩展,甚至还增加了一些外部程序。
当然了,大部分的HiveQL还是很常见的。本章以及随后的几章将会使用一些典型的例子来讲解HiveQL的那些特性。在某些情况下.我们会从整体上简要地谈到一些细节,然后会在后面的章节里再去比较完整地进行讨论。
本章开始的部分是HiveQL所谓的数据定义语言部分,其用于创建、修改和删除数据库、表、视图、函数和索引。

1、Hive中的数据库

Hive中数据库的概念本质上仅仅是表的一个目录或者命名空间。然而,对于具有很多组和用户的大集群来说,这是非常有用的,因为这样可以避免表命名冲突。通常会使用数据库来将生产表组织成逻辑组。
如果用户没有显式指定数据库,那么将会使用默认的数据库default。
下面这个例子就展示了如何创建一个数据库:

hive>CREATE DATABASE financials;

如果数据库financials已经存在的话,那么将会抛出一个错误信息。使用如下语句可以避免在这种情况下抛出错误信息:

hive>CREATE DATABASE IF NOT EXISTS financials;

虽然通常情况下用户还是期望在同名数据库已经存在的情况下能够抛出警告信息的,但是IFNOTEXISTS这个子句对于那些在继续执行之前需要根据需要实时创建数据库的情况来说是非常有用的。

在所有的数据库相关的命令中,都可以使用SCHEMA这个关键字来替代关键字TABLE。
随时可以通过如下命令方式查看Hive中所包含的数据库:

hive>SHOW DATABASES;
default
financials
hive>CREATE DATABASE human_resources;
hive>SHOW DATABASES;
default
financials
human_resources

如果数据库非常多的话,那么可以使用正则表达式匹配来筛选出需要的数据库名,正则表达式这个概念,将会在以后介绍。下面这个例子展示的是列举出所有以字母h开头,以其他字符结尾(即.*部分含义)的数据库名:

hive>SHOW DATABASES LIKE 'h.*';
human_resources

Hive会为每个数据库创建一个目录。数据库中的表将会以这个数据库目录的子目录形式存储。有一个例外就是default数据库中的表,因为这个数据库本身没有自己的目录。

数据库所在的目录位于属性hive.metastore.warehouse.dir所指定的顶层目录之后。假设用户使用的是这个配置项默认的配置,也就是/user/hive/warehouse,那么当我们创建数据库financials时,Hive将会对应地创建一个目录/user/hive/warehouse/financials.db。这里请注意,数据库的文件目录名是以.db结尾的。

用户可以通过如下的命令来修改这个默认的位置:

hive>CREATE DATABASE financials
    >LOCATION '/my/preferred/directory';

用户也可以为这个数据库增加一个描述信息,这样通过DESCRIBE DATABASE <database>命令就可以查看到该信息。

hive>CREATE DATABASE financials
    >COMMENT 'Holds all financial tables';
hive>DESCRIBE DATABASE financials;
financials Holds all financial tables  hdfs://master-server/user/hive/warehouse/financials.db

从上面的例子中,我们可以注意到,DESCRIEB DATABASE语句也会显示出这个数据库所在的文件目录位置路径。在这个例子中,URI格式是hdfs。如果安装的是MapR,那么这里就应该是maprfs。
前面DESCRIBE DATABASE语句的输出中,我们使用了master-server来代表URI权限,也就是说应该是由文件系统的“主节点"(例如,HDFS中运行NameNode服务的那台服务器)的服务器名加上一个可选的端口号构成的(例如,服务器名:端口号这样的格式)。
如果用户执行的是伪分布式模式,那么主节点服务器名称就应该是localhosto对于本地模式,这个路径应该是一个本地路径,例如file:///user/hive/warehous/financials.db。如果这部分信息省略了,那么会使用Hadoop配置文件中的配置项fs.default.name作为master-server所对应的服务器名和端口号,这个配置文件可以在$HADOOP_HOME/conf这个目录下找到。

需要明确的是,hdfs:///user/hive/warehouse/financials.db和hdfs://master-server/user/hive/warehouse/financials.db是等价的,其中master-server是主节点的DNS名和可选的端口号。
为了保持完整性,当用户指定一个相对路径(例如,some/relative/path)时,对于HDFS和Hive,都会将这个相对路径放到分布式文件系统的指定根目录下(例如,hdfs:///user/<user-name>)。然而,如果用户是在本地模式下执行的话,那么当前的本地工作目录将是some/relative/path的父目录。
为了脚本的可移植性,通常会省略掉那个服务器和端口号信息,而只有在涉及到另一个分布式文件系统实例的时候才会指明该信息。
此外,用户还可以为数据库增加一些和其相关的键.值对属性信息,尽管目前仅有的功能就是提供了一种可以通过DESCRIBE DATABASE EXTENDED <database>语句显示出这些信息的方式:

hive> CREATE DATABASE financials
    > WITH DBPROPERTIES('creator'='Mark Moneybags','date'='2012-01-02';
hive> DESCRIBE DATABASE financials;
financials hdfs://master-server/user/hive/warehouse/financials.db
hive>DESCRIBE DATABASE EXTENDED financials;
financials hdfs://master-server/user/hive/warehouse/financials.db
(date=2012-01-02,creator=Mark Moneybags);

USE命令用于将某个数据库设置为用户当前的工作数据库,和在文件系统中切换工作目录是一个概念:

hive>USE financials;

现在,使用像SHOW TABLES这样的命令就会显示当前这个数据库下所有的表。
不幸的是,并没有一个命令可以让用户查看当前所在的是哪个数据库!幸运的是,在Hive中是可以重复使用USE...命令的,这是因为在Hive中并没有嵌套数据库的概念。
可以回想下,可以通过设置一个属性值来在提示符里面显示当前所在的数据库:

hive>set hive.cli.print.current.db=true;
hive(financials)>USE default;
hive(default)>set hive.cii.print.current.db=false;

最后,用户可以删除数据库:

hive>DROP DATABASE IF EXISTS financials;

IFEXISTS子句是可选的,如果加了这个子句,就可以避免因数据库finanacials不存在而抛出警告信息。
默认情况下,Hive是不允许用户删除一个包含有表的数据库的。用户要么先删除数据库中的表,然后再删除数据库;要么在删除命令的最后面加上关键字CASCADE,这样可以使Hive自行先删除数据库中的表:

hive>DROP DATABASE IF EXISTS financials CASCADE;

如果使用的是RESTRICT这个关键字而不是CASCADE这个关键字的话,那么就和默认情况一样,也就是,如果想删除数据库,那么必须先要删除掉该数据库中的所有表。如果某个数据库被删除了,那么其对应的目录也同时会被删除。

2、修改数据库

用户可以使用ALTER DATABASE命令为某个数据库的DBPROPERTIES设置键-值对属性值,来描述这个数据库的属性信息。数据库的其他元数据信息都是不可更改的,包括数据库名和数据库所在的目录位置:
hive>ALTER DATABASE financials SET DBPROPERTIES('edited-by'='JoeDba');
没有办法可以删除或者“重置”数据库属性。

3、创建表

CREATE TABLE语句遵从SQL语法惯例,但是Hive的这个语句中具有显著的功能扩展,使其可以具有更广泛的灵活性。

建表语法
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]

说明:
1、    CREATE TABLE 创建一个指定名字的表。如果相同名字的表已经存在,则抛出异常;用户可以用 IF NOT EXISTS 选项来忽略这个异常。
2、    EXTERNAL关键字可以让用户创建一个外部表,在建表的同时指定一个指向实际数据的路径(LOCATION),Hive 创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径,不对数据的位置做任何改变。在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据。
3、    LIKE 允许用户复制现有的表结构,但是不复制数据。
4、    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, ...)]
用户在建表的时候可以自定义 SerDe 或者使用自带的 SerDe。如果没有指定 ROW FORMAT 或者 ROW FORMAT DELIMITED,将会使用自带的 SerDe。在建表的时候,用户还需要为表指定列,用户在指定表的列的同时也会指定自定义的 SerDe,Hive通过 SerDe 确定表的具体的列的数据。
5、    STORED AS 
SEQUENCEFILE|TEXTFILE|RCFILE
如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE。

6、CLUSTERED BY
对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive也是 针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。 
把表(或者分区)组织成桶(Bucket)有两个理由:
(1)获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接 (Map-side join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。
(2)使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。


例如,可以定义表的数据文件存储在什么位置、使用什么样的存储格式,等等。本节中,我们会讨论其他一些在CREATE TABLE语句中可以使用到的选项,下面这个表结构适用于前面我们在前面“集合数据类型"中所声明的employees表:

CREATE TABLE IF NOTEXISTS mydb.employees(
     name             STRING COMMENT 'Employee name',
     salary             FLOAT COMMENT 'Employee salary',
     subordinates  ARRAY<STRING> COMMENT 'Names Of subordinates'
     deductions     MAP<STRING,FLOAT> COMMENT 'Keys are deductions names,values are percentages',
     address         STRUCT<street:STRING,city:STRING,state:STRING,zip:INT> COMMENT 'Home address'
) COMMENT 'Description Of the table'
TBLPROPERTIES ('creator'='me','created_at'='2012-01-02 10:00:00'
LOCATION '/user/hive/warehouse/mydb.db/employees';

首先,我们可以注意到,如果用户当前所处的数据库并非是目标数据库,那么用户是可以在表名前增加一个数据库名来进行指定的,也就是例子中的mydb。如果用户增加上可选项IF NOT EXITS,那么若表己经存在了,Hive就会忽略掉后面的执行语句,而且不会有任何提示。在那些第一次执行时需要创建表的脚本中,这么与是非常有用的。

然而,这个语句还有一个用户需要注意的问题。如果用户所指定的表的模式和已经存在的这个表的模式不同的话,Hive不会为此做出提示。如果用户的意图是使这个表具有重新指定的那个新的模式的话,那么就需要先删除这个表,也就是丢弃之前的数据,后再重建这张表。用户可以考虑使用一个或多个ALTERTABLE语句来修改已经存在的表的结构。
提示:如果用户使用了IF NOT EXISTS,而且这个已经存在的表和CREATE TABLE语句后指定的模式是不同的。Hive会忽略掉这个差异。
用户可以在字段类型后为每个字段增加一个注释。和数据库一样,用户也可以为这个表本身添加一个注释,还可以自定义一个或多个表属性。大多数情况下,TBLPROPERTIES的主要作用是按键·值对的格式为表增加额外的文档说明。但是,当我们检查Hive和像DymamoDB这样的数据库间的集成时,我们可以发现TBLPROPERTIES还可用作表示关于数据库连接的必要的元数据信息。
Hive会自动增加两个表属性:一个是last_modified_by,其保存着最后修改这个表的用户的用户名,另一个是last_modified_time,其保存着最后一次修改的新纪元时间秒。
提示:SHOW TBLPROPERTIES table_name命令,用于列举出某个表的TBLPROPERTIES属性信息。
最后,可以看到我们可以根据情况为表中的数据指定一个存储路径(和元数据截然不同的是,元数据总是会保存这个路径)。在这个例子中,我们使用的Hive将会使用的默认的路径/user/hive/warehouse/mydb.db/employees。其中,/user/hive/warehouse是默认的“数据仓库”路径地址(这个之前有讨论过),mydb.db是数据库目录,employees是表目录。
默认情况下,Hive总是将创建的表的目录放置在这个表所属的数据库目录之后。不过,default数据库是个例外,其在/user/hive/warehouse下并没有对应一个数据库目录。因此default数据库中的表目录会直接位于/user/hive/warehouse目录之后(除了用户明确指定为其他路径)。
提示:为了避免潜在产生混淆的可能性,如果用户不想使用默认的表路径,那么最好是使用外部表。
用户还可以拷贝一张己经存在的表的表模式(而无需拷贝数据):

CREATE TABLE IF NOT EXISTS mydb.employees2 LIKE mydb.employees;

hive还可以接受可选的LOCATION语句,但是注意其他的属性,包括模式,都是不可能重新定义的,这些信息直接从原始表获得。
SHOW TABLES命令可以列举出所有的表。如果不增加其他参数,那么只会显示当前工作数据库下的表。假设我们已经创建了一些其他表,table1和table,而且我们的工作数据库是mydb:

hive>USE mydb;
hive>SHOW TABLES;
employees
table1
table2

即使我们不在那个数据库下,我们还是可以列举指定数据库下的表的:

hive>USE default;
hive>SHOW TABLES IN mydb;
employees
tablel
tab1e2

如果我们有很多的表,那么我们可以使用正则表达式来过滤出所需要的表名。

hive>USE mydb;
hive>SHOW TABLES 'empl.*';
employees

Hive并非支持所有的正则表达式功能。如果用户了解正则表达式的话,最好事先测试下备选的正则表达式是否真正奏效!
单引号中的正则表达式表示的是选择所有以empl开头并以其他任意字符(也就是.*部分)结尾的表名。
提示:IN databasename语句和对表名使用正则表达式两个功能尚不支持同时使用。

我们也可以使用DESCRIBE EXTENDED mydb.employees命令来查看这个表的详细表结构信息。(如果我们当前所处的工作数据库就是mydb的话,那么我们可以不加mydb.这个前缀。)

hive>DESCRIBE EXTENDED mydb.employees;

使用FORMATTED关键字替代EXTENDED关键字的话,可以提供更加可读的和冗长的输出信息。


上面输出信息的第一段是和没有使用关键字EXTENDED或者FROMATTED时输出的结果一样的(例如,只输出包含有列描述信息的表结构信息)。
如果用户只想查看某一个列的信息,那么只要在表名后增加这个字段的名称即可。这种情况下,使用EXTENDED关键字也不会增加更多的输出信息:

hive>DESCRIBE mydb.employees.salary;
salary float Employee salary

回到之前的那个包含详细扩展信息的输出。我们需要特别注意以location:开头的那行描述信息。这个是Hive在HDFS中的存储表中数据的完整的URL目录路径,这个我们之前有讨论过的。
我们说过last_modified_by和last_modified_time两个表属性是会自动创建的。如果没有定义任何的用户自定义表属性的话,那么它们也不会显示在表的详细信息中!

3.1、管理表

我们目前所创建的表都是所谓的管理表,有时也被称为内部表。因为这种表,Hive会(或多或少地)控制着数据的生命周期。正如我们所看见的,Hive默认情况下会将这些表的数据存储在由配置项hive.metastore.warehouse.dir(例如,/user/hive/warehouse)所定义的目录的子目录下。
当我们删除一个管理表时,Hive也会删除这个表中数据。
但是,管理表不方便和其他工作共享数据。例如,假设我们有一份由Pig或者其他工具创建并且主要由这一工具使用的数据,同时我们还想使用Hive在这份数据上执行一些查询,可是并没有给予Hive对数据的所有权,我们可以创建一个外部表指向这份数据,而并不需要对其具有所有权。

3.2、外部表

假设我们正在分析来自股票市场的数据。我们会定期地从像lnfochimps(http://infochimps.come/datasets)这样的数据源接人关于NASDAQ和NYSE的数据,然后使用很多工具来分析这份数据。(我们可以看到数据集名称分别为infochimps_datasets_4777_download_16185和infochimps_datasets_4778_download_16677,实际上该数据来源于Yahoo!财经。)
我们后面将要使用的模式和这2份源数据都是匹配的。我们假设这些数据文件位于分布式文件系统的/data/stocks目录下。
下面的语句将创建一个外部表,其可以读取所有位于/data/stocks目录下的以逗号分隔的数据:

CREATE EXTERNAL TABLE IF NOT EXISTS stocks(
   exchange               STRING,
   symbol                   STRING,
   ymd                        STRING,
   prince_open           FLOAT,
   price_high              FLOAT,
   price_low               FLOAT,
   price_close            FLOAT,
   volume                   INT,
   price_adj_close     FLOAT
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/data/stocks';

关键字EXTENAL告诉Hive这个表是外部的,而后面的LOCATion一子句则用于告诉Hive数据位于哪个路径下。
因为表是外部的,所以Hive并非认为其完全拥有这份数据。因此,删除该表并不会删除掉这份数据,不过描述表的元数据信息会被删除掉。

管理表和外部表有一些小小的区别,那就是,有些HiveQL语法结构并不适用于外部表。后面当我们遇到这些问题的时候,我们再来进行讲述。然而,我们需要清楚的重要的一点是管理表和外部表之间的差异要比刚开始所看到的小得多。即使对于管理表,用户也是可以知道数据是位于哪个路径下的,因此用户也是可以使用其他工具(例如hadoop的dfs命令等)来修改甚至删除管理表所在的路径目录下的数据的。可能从严格意义上来说,Hive是管理着这些目录和文件,但是其并非具有对它们的完全控制权限!回想下,Hive实际上对于所存储的文件的完整性以及数据内容是否和表模式相一致并没有支配能力,甚至管理表都没有给用户提供这些管理能力。
尽管如此,好的软件设计的一般原则是表达意图。如果数据会被多个工具共享,那么可以创建一个外部表,来明确对数据的所有权。用户可以在DESCRIBEEXTENDEDtablename语句的输出中查看到表是否是管理表或外部表。在末尾的详细表信息输出中,对于管理表,用户可以看到如下信息:
... tableType:MANAGED_TABLE)
对于外部表,用户可以查看到如下信息:
... tabIeType:EXTERNAL_TABLE)
对于管理表,用户还可以对一张存在的表进行表结构复制(而不会复制数据):

CREATE EXTERNAL TABLE IF NOT EXISTS mydb.employees3
LIKE mydb.employees
LOCATION '/path/to/data';

提示
这里,如果语句中省喀掉EXTERNAL关键字而且源表是外部表的话,那么生成的新表也将是外部表·如果语句中省略掉EXTERNAL关健字而且源表是管理表的话,那么生成的新表也将是管理表·但是,如果语句中包含有EXTERNAL关键字而且源表是管理表的话,那么生成的新表将是外部表·即使在这种场景下,LOCATION子句同样是可选的。

4、分区表、管理表

数据分区的一般概念存在已久。其可以有多种形式,但是通常使用分区来水平分散压力,将数据从物理上转移到和使用最频繁的用户更近的地方,以及实现其他目的。
Hive中有分区表的概念。我们可以看到分区表具有重要的性能优势,而且分区表还可以将数据以一种符合逻辑的方式进行组织,比如分层存储。
我们首先会讨论下分区管理表。重新来看之前的那张emplyees表并假设我们在一个非常大的跨国公司工作。我们的HR人员经常会执行一些带WHERE语句的查询,这样可以将结果限制在某个特定的国家或者某个特定的第一级细分(例如'美国的州,或者加拿大的省)。为简单起见我们将只使用到state(州)。在address(住址)字段中已经重复包含了州信息。这和state分区是不同的。我们可以在字段address中删除state元素。查询中并不会造成模糊不清的问题,因为我们需要通过使用address.state才能调用到address中这个元素的值。那么,让我们先按照country(国家)再按照state(州)来对数据进行分区吧:

CREATE TABLE employees(
      name                 STRING,
      salary                FLOAT,
      subordinates     ARRAY<STRING>,
      deductions        MAP<STRING,FLOAT>,
      address            STRUCT<street:STRING,city:STRING,state:STRING,zip:INT>
)
PARTITIONED BY (country STRING,state STRING);

分区表改变了Hive对数据存储的组织方式。如果我们是在mydb数据库中创建的这个表,那么对于这个表只会有一个employees目录与之对应:hdfs://master_server/user/hive/warehouse/mydb.db/employees
但是,Hive现在将会创建好可以反映分区结构的子目录。例如:

.../empIoyees/country=CA/state=AB
.../empIoyees/country=CA/state=BC
.../empIoyees/country=US/state=AL
.../employees/country=US/state=AK
是的,那些是实际的目录名称。州目录下将会包含有零个文件或者多个文件,这些文件中存放着那些州的雇员信息。
分区字段〈这个例子中就是country和state)一旦创建好,表现得就和普通的字段一样。这里有一个己知的异常情况,是由一个bug引起的。事实上,除非需要优化查询性能,否则使用这些表的用户不需要关心这些“字段”是否是分区字段。
例如,下面这个查询语句将会查找出在美国伊利诺斯州的所有雇员:

SELECT * FROM employees WHERE country='US' AND state='IL';

需要注意的是,因为country和state的值已经包含在文件目录名称中了,所以也就没有必要将这些值存放到它们目录下的文件中了。事实上,数据只能从这些文件中获得,因此用户需要在表的模式中说明这点,而且这个数据浪费空间。
对数据进行分区,也许最重要的原因就是为了更快地查询。在前面那个将结果范围限制在伊利诺斯州的雇员的查询中,仅仅需要扫描一个目录下的内容即可。即使我们有成千上万个国家和州目录,除了一个目录其他的都可以忽略不计。对于非常大的数据集,分区可以显著地提高查询性能,除非对分区进行常见的范围筛选(例如,按照地理位置范围或按照时间范围等)。
当我们在WHERE子句中增加谓词来按照分区值进行过滤时,这些渭词被称为分区过滤器。
即使你做一个跨越整个美国的查询,Hive也只会读取65个文件目录,其中包含有50个州,9大地区,以及哥伦比亚特区和6个军事属地。当然,如果用户需要做一个查询,查询对象是全球各地的所有员工,那么这也是可以做到的。Hive会不得不读取每个文件目录,但这种宽范围的磁盘扫描还是比较少见的。
但是,如果表中的数据以及分区个数都非常大的话,执行这样一个包含有所有分区的查询可能会触发一个巨大的MapReduce任务。一个高度建议的安全措施就是将Hive设置为“strict(严格)”模式,这样如果对分区表进行查询而WHERE子句没有加分区过滤的话,将会禁止提交这个任务。用户也可以按照下面的语句将属性值设置为"nostrict(非严格)”:

hive>set hive.mapred.mode=strict;
hive>SELECT e.name,e.salary FROM employees e LIMIT 100;
FAILED:Error in semantic analysis:NO partition predicate found for Alias "e" Table "employees"
hive>set hive.mapred.mode=nonstrict;
hive>SELECT e.name,e.salary FROM employees e LIMIT100:
John Doe 100000.0
...

可以通过SHOW PARTITIONS命令查看表中存在的所有分区:

hive>SHOW PARTITIONS employees;
...
country=CA/state=AB
country=CA/state=BC
...

如果表中现在存在很多的分区,而用户只想查看是否存储某个特定分区键的分区的话,用户还可以在这个命令上增加一个指定了一个或者多个特定分区字段值的PARTITION子句,进行过滤查询:

hive>SHOW PARTITIONS employees PARTITION(country='US');
country=US/state=AL
country=US/state=AK
hive>SHOW PARTITIONS employees PARTITION(country='US',state='AK');
country=US/state—AK

DESCRIBE EXTENDED employees命令也会显示出分区键:

hive>DESCRIBEEXTENDED employees;
name     string,
....
....
Detailed Table lnformation.
partitionKeys:[FieldSchema(name:country,type:string,comment:null),
FieldSchema(name:state,type:string,comrnent:null)],
....

输出信息中的模式信息部分会将country和state以及其他字段列在一起,因为就查询而言,它们就是字段。Detailed Table lnformation(详细表信息)将和state作为分区键处理。这两个键当前的注释都是null,我们也可以像给普通的字段增加注释一样给分区字段增加注释。
在管理表中用户可以通过载人数据的方式创建分区。如下例中的语句在从一个本地目录($HOME/california-employees)载人数据到表中的时候,将会创建一个US和CA(表示加利福尼亚州)分区。用户需要为每个分区字段指定一个值。请注意我们在HiveQL中是如何引用HOME环境变量的:

LOAD DATA LOCAL INPATH '$(env:HOME)/california-employees'
INTO TABLE employees
PARTITION(country='US',state='CA';

Hive将会创建这个分区对应的目录.../employees/country=US/state=CA,而且$HOME/california-employees这个目录下的文件将会被拷贝到上述分区目录下。

4.1、外部分区表

外部表同样可以使用分区。事实上,用户可能会发现,这是管理大型生产数据集最常见的情况。这种结合给用户提供了一个可以和其他工具共享数据的方式,同时也可以优化查询性能。因为用户可以自己定义目录结构,因此用户对于目录结构的使用具有更多的灵活性。稍后我们将看到一个特别有用的例子。
我们举一个新例子,非常适合这种场景,即日志文件分析。对于日志信息,大多数的组织使用一个标准的格式,其中记录有时间戳、严重程度(例如ERROR、WARTING、INFO),也许还包含有服务器名称和进程ID,然后跟着一个可以为任何内容的文本信息。假设我们是在我们的环境中进行数据抽取、数据转换和数据装载过程(ETL),以及日志文件聚合过程的,将每条日志信息转换为按照制表键分割的记录,并将时间戳解析成年、月和日3个字段,剩余的hms部分(也就是时间戳剩余的小时、分钟和秒部分)作为一个字段,因为这样显得清楚多了。一种方式是用户可以使用Hive或者Pig内置的字符串解析函数来完成这个日志信息解析过程。另一种方式是,我们可以使用较小的数值类型来保存时间戳相关的字段以节省空间。这里,我们没有采用后面的解决办法。
我们可以按照如下方式来定义对应的Hive表:

CREATE EXTERNAL TABLE IF NOT EXISTS log_messages(
      hms                    INT,
      severity              STRING,
      server                STRING,
      process_id         INT,
      message           STRING)
PARTITIONED BY (year INT,month INT,day INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY'\t';

我们现在假定将日志数据按照天进行划分,划分数据尺寸合适,而且按天这个粒度进行查询速度也足够快。
回想下,之前我们创建过一个非分区外部表,是一个股票交易表,那时要求使用一个LOCATION子句。对于外部分区表则没有这样的要求。有一个ALTER TABLE语句可以单独进行增加分区。这个语句需要为每一个分区键指定一个值,本例中,也就是需要为year、month和day这3个分区键都指定值。下面是一个例子,演示如何增加一个2012年1月2日的分区:

ALTER TABLE log_messages ADD PARTITION (year=2012,month=1,day=2)
LOCATION 'hdfs://master_server/data/log_messages/2012/01/02';

我们使用的目录组织习惯完全由我们自己定义。这里,我们按照分层目录结构组织,因为这是一个合乎逻辑的数据组织方式,但是并非要求一定如此。我们可以遵从Hive的目录命名习惯(例如...exchange=NASDAQ,symbol/AAPL),但是也并非要求一定如此。
顺便说一下,Hive不关心一个分区对应的分区目录是否存在或者分区目录下是否有文件。如果分区目录不存在或分区目录下没有文件,则对于这个过滤分区的查询将没有返回结果。当用户想在另外一个进程开始往分区中写数据之前创建好分区时,这样做是很方便的。数据一旦存在,对于这份数据的查询就会有返回结果。
这个功能所具有的另一个好处是:可以将新数据写人到一个专用的目录中,并与位于其他目录中的数据存在明显的区别。同时,不管用户是将旧数据转移到一个“存档”位置还是直接删除掉,新数据被篡改的风险都被降低了,因为新数据的数据子集位于不同的目录下。
和非分区外部表一样,Hive并不控制这些数据。即使表被删除,数据也不会被删除。
和分区管理表一样,通过SHOW PARTITIONS命令可以查看一个外部表的分区:

hive>SHOW PARTITIONS log_gmessages;
year=2011/month=12/day=31
year=2012/month=1/day=1
year=2012/month=1/day=2

同样地,DESCRIBE EXTENDED log_messages语句会将分区键作为表的模式的一部分,和partitionKeys列表的内容同时进行显示:

hive>DESCRIBE EXTENDED log_messages;



这个输出缺少了一个非常重要的信息,那就是分区数据实际存在的路径。这里有一个路径字段,但是该字段仅仅表示如果表是管理表其会使用到的Hive默认的目录。不过,我们可以通过如下方式查看到分区数据所在的路径:

hive>DESCRIBE EXTENDED log_messages PARTITION (year=2012,month=1,day=2);
...
location:s3n://ourbucket/logs/2011/01/02,
...

我们通常会使用分区外部表,因为它具有非常多的优点,例如逻辑数据管理、高性能的查询等。

ALTER TABLE ... ADD PARTITION语句并非只有对外部表才能够使用。对于管理表,当有分区数据不是由我们之前讨论过的LOAD和INSERT语句产生时,用户同样可以使用这个命令指定分区路径。用户需要记住并非所有的表数据都是放在通常的Hive "warehouse”目录下的,同时当删除管理表时,这些数据不会连带被删除掉!因此,从“理智的”角度来看,是否敢于对管理表使用这个功能是一个问题。

4.2、自定义表的存储格式

我们谈论过Hive的默认存储格式是文本文件格式,这个也可以通过可选的子句STORED AS TEXTFILE显式指定,同时用户还可以在创建表时指定各种各样的分隔符。这里我们重新展示下之前讨论过的那个employees表:

CREATE TABLE employees(
   name              STRING,
   salary              FLOAT,
   subordinates   ARRAY<STRING>,
   deductions      MAP<STRING,FLOAT>,
   address           STRUCT<street:STRING,city:STRING,state:STRING,zip:INT)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;

TEXTFILE意味着所有字段都使用字母、数字、字符编码,包括那些国际字符集,尽管我们可以发现Hive默认是使用不可见字符来作为“终结者”(分隔符)的。使用TEXTFILE就意味着,每一行被认为是一个单独的记录。
用户可以将TEXTFILE替换为其他Hive所支持的内置文件格式,包括SEQUENCEFILE和RCFILE,这两种文件格式都是使用二进制编码和压缩(可选)来优化磁盘空间使用以及/0带宽性能的。
对于记录是如何被编码成文件的,以及列是如何被编码为记录的,Hive指出了它们之间的不同。用户可以分别自定义这些行为。
记录编码是通过一个inputfomat对象来控制的(例如TEXTFILE后面的Java代码实现)。Hive使用了一个名为org.apache.hadoop.mapred.TextInputFormat的Java类(编译后的模块)。如果用户不熟悉Java的话,这种点分割的命名语法表明了包的一个分层的树形命名空间,这个结构和Java代码的目录结构是对应的。最后一个名字,TextInputFormat,是位于最顶层包mapred下的一个类。
记录的解析是由序列化器/反序列化器(或者缩写为SerDe)来控制的。对于TEXTFILE,Hive所使用的SerDe是另外一个被称为org.apache.hadoop.hive.serde2.lazy.LazySimpIeSerDe的Java类。
为了保持完整性,Hive还使用一个叫做outputformat的对象来将查询的输出写人到文件中或者输出到控制台。对于TEXTFILE,用于输出的Java类名为org.apache.hadoop.hive.qI.io.HiveIgnoreKeyTextOutputFormat。

提示:Hive使用一个inputformat对象将输入流分割成记录,然后使用一个OutputFormat对象来将记录格式化为输出流(例如查询的输出结果),再使用一个SerDe在读数据时将记汞解析成列,在写数据时将列编码成记录。
用户还可以指定第三方的输人和输出格式以及serDe,这个功能允许用户自定义Hive本身不支持的其他广泛的文件格式。

这里有一个使用了自定义SerDe、输人格式和输出格式的完整的例子,其可以通过Avro协议访问这些文件:

CREATE TABLE kst PARTITIONED BY(ds string)
ROW FORMAT SERDE 'com.linkedin.haivvreo.AvroSerDe'
WITH SERDEPROPERTIES('schema.url'='http://schema_provider/kst.avsc')
STORED AS
INPUTFORMAT 'com.linkedin.haivvreo.AvroContainerInputFormat'
OUTPUTFORMAT 'com/linkedin.haivvreo.AvroContainerOutputFormat';

ROW FORMAT SERDE...指定了使用的SerDe。Hive提供了WITH SERDEPROPERTIES功能,允许用户传递配置信息给SerDeoHive本身并不知晓这些属性的含义,需要SerDe去决定这些属性所代表的含义。需要注意的是,每个属性名称和值都应该是带引号的字符串。
STORED AS INPUTFORMAT...OUTPUTFORMAT...子句分别指定了用于输人格式和输出格式的Java类。如果要指定,用户必须对输人格式和输出格式都进行指定。
需要注意的是,DESCRIBE EXTENDED table命令会在DETAILED TABLE INFORMATION部分列举出输人和输出格式以及SerDe和SerDe所自带的属性信息。
对于我们的例子,我们可以查看到如下信息:

hive>DESCRIBE EXTENDED kst
...
inputFormat:com.linkedin.haivvreo.AvroContainerInputFormat,
outputFormat:com·linkedin.haivvreo.AvroContainerOutputFormat,
serdelnfo:SerDeInfo(name:null,
serializationLib:com.linkedin.haivvreo.AvroSerDe,
   parameters:{schema.url—http://schema_provider/kst.avsc})
...

最后,还有一些额外的CREATE TABLE子句来更详细地描述数据期望是按照什么样子存储的。我们重新扩展下在第3.2节“外部表”中使用到的stocks表:

CREATE EXTERNAL TABLE IF NOT EXISTS stocks(
    exchange           STRING,
    symbol               STRING,
    ymd                   STRING,
    price_open        FLOAT,
    price_high          FLOAT,
    price_low           FLOAT,
    price_close        FLOAT,
    volume               INT,
    price_adj_close FLOAT)
CLUSTERED BY (exchange,symbol)
SORTED BY (ymd ASC)
INTO 96 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/data/stocks';

CLUSTERED BY ... INTO ... BUCKETS子句还可以后接一个可选的SORTED BY ... 子句,用来优化某些特定类型的查询。

5、删除表

Hive支持和SQL中DROP TABLE命令类似的操作:

DROP TABLE IF EXISTS employees;

可以选择是否使用IF EXITST关键字。如果没有使用这个关键字而且表并不存在的话,那么将会抛出一个错误信息。
对于管理表,表的元数据信息和表内的数据都会被删除。

提示
事实上,如果用户开启了Hadoop回收站功能(这个功能默认是关闭的),那么数据将会被转移到用户在分布式文件系统中的用户根目录下的.Trash目录下,也就是HDFS中的/user/$USER/.Trash目录。如果想开启这个功能,只需要将配置属性fs。trash.interval的值设置为一个合理的正整数即可。这个值是“回收站检查点”间的时间间隔,单位是分钟。因此如果设置值为1440那么就表示是24小时。不过并不能保证所有的分布式系统以及所有版本都是支持的这个功能的·如果用户不小心删除了一张存储着重要数据的管理表的话,那么可以先重建表,然后重建所需要的分区,再从.Trash目录中将误删的文件移动到正确的文件目录下(使用文件系统命令)来重新存储数据。
对于外部表,表的元数据信息会被删除,但是表中的数据不会被删除。

6、修改表

大多数的表属性可以通过ALTER TABLE语句来进行修改。这种操作会修改元数据,但不会修改数据本身。这些语句可用于修改表模式中出现的错误、改变分区路径,以及其他一些操作。

警告:ALTERTABLE仅仅会修改表元数据,表数据本身不会有任何修改。需要用户自己确认所有的修改都和真实的数据是一致的。

6.1、表重命名

使用以下这个语句可以将表log_messages重命名为logmsgs:

ALTER TABLE log_messages RENAME TO logmsgs;

6.2、增加、修改和删除表分区

正如我们前面所见到的,ALTER TABLE table ADD PARTITION ... 语句用于为表(通常是外部表)增加一个新的分区。这里我们增加可提供的可选项,然后多次重复前面的分区路径语句:

ALTER TABLE log_messages ADD IF NOT EXISTS
PARTITION (year=2011,month=1,day=1) LOCATION '/logs/2011/01/01'
PARTITION (year=2011,month=1,day=2) LOCATION '/logs/2011/01/02'
PARTITION (year=2011,month=1,day=3) LOCATION '/logs/2011/01/03'

当使用Hive v0.8.0或其后的版本时,在同一个查询中可以同时增加多个分区。一如既往,IF NOT EXISTS也是可选的,而且含义不变。

同时,用户还可以通过高效地移动位置来修改某个分区的路径:

ALTER TABLE log_messages PARTITION(year=2011,month=12,day=2)
SET LOCATION 's3n://ourbucket/logs/2011/01/02';

这个命令不会将数据从旧的路径转移走,也不会删除旧的数据。
最后,用户可以通过如下语句删除某个分区:

ALTER TABLE log_messages DROP IF EXISTS PARTITION(year=2011,month=12,day=2);

按照常规,上面语句中的IF EXISTS子句是可选的。对于管理表,即使是使用ALTER TABLE … ADD PARTITION语句增加的分区,分区内的数据也是会同时和元数据信息一起被删除的。对于外部表,分区内数据不会被删除。

6.3、修改列信息

用户可以对某个字段进行重命名,并修改其位置、类型或者注释:

ALTER TABLE log_messages CHANGE COLUMN hms hours_minutes_seconds INT
COMMENT 'The hours,minutes and seconds part of the timestamp'
AFTER severity;

即使字段名或者字段类型没有改变,用户也需要完全指定旧的字段名,并给出新的字段名及新的字段类型。关键字COLUMN和COMMENT子句都是可选的。前面所演示的例子中,我们将字段转移到severity字段之后。如果用户想将这个字段移动到第一
个位置,那么只需要使用FIRST关键字替代AFTER other_column子句即可。和通常一样,这个命令只会修改元数据信息。如果用户移动的是字段,那么数据也应当和新的模式匹配或者通过其他某些方法修改数据以使其能够和模式匹配。

6.4、增加列

用户可以在分区字段之前增加新的字段到已有的字段之后。

ALTER TABLE log_messages ADD COLUMNS (
   app_name STRING COMMENT 'AppIication name',
   session_id LONG COMMENT 'The current sessionid' );

COMMENT子句和通常一样,是可选的。如果新增的字段中有某个或多个字段位置是错误的,那么需要使用ALTER COULME表名CHANGE COLUMN语句逐一将字段调整到正确的位置。

6.5、删除或者替换列

下面这个例子移除了之前所有的字段并重新指定了新的字段:

ALTER TABLE log_messages REPLACE COLUMNS (
    hours_mins_secs INT COMENT 'hour,minute,seconds from timestamp'
    severity  STRING COMMENT 'The message severity'
    message  STRING COMMENT 'The rest of the message' );

这个语句实际上重命名了之前的hms字段并且从之前的表定义的模式中移除了字段server和process_id。因为是alter语句,所以只有表的元数据信息改变了。
REPLACE语句只能用于使用了如下2种内置SerDe模块的表:DynamicSerDe或者MetadataTypedColumnsetSerDe。回想一下,SerDe决定了记录是如何分解成字段的(反序列化过程)以及字段是如何与人到存储中的(序列化过程)。

6.6、修改表属性

用户可以增加咐加的表属性或者修改已经存在的属性,但是无法删除属性:

ALTER TABLE log_messages SET TBLPROPERTIES ('notes'='The process id is no longer captured;this column is always NULL');

6.7、修改存储属性

有几个ALTER TABLE语句用于修改存储格式和SerDe属性。
下面这个语句将一个分区的存储格式修改成了SEQUENCEFILE,在“创建表”中讨论过存储格式:

ALTER TABLE log_messages PARTITION(year=2012,month=1,day=1) SET FILEFORMAT SEQUENCEFILE;

如果表是分区表,那么需要使用PARTITION子句。
用户可以指定一个新的SerDe,并为其指定SerDe属性,或者修改已经存在的SerDe的属性。下面这个例子演示的表使用了一个名为com.example.JSONSerDe的Java类来处理记录使用JSON编码的文件:

ALTER TABLE table_using_JSON_storage SET SERDE 'com.example.JSONSerDe'
WITH SERDEPROPERTIES ('prop1'='value1' , 'prop2'='value2');

SERDEPROPERTIES中的属性会被传递给SerDe模块(本例中,也就是com.example.JSONSerDe这个Java类)。需要注意的是,属性名(例如prop1)和属性值(例如value1)都应当是带引号的字符串。
SERDEPROPERTIES这个功能是一种方便的机制,它使得SerDe的各种实现都允许用户进行自定义。
下面这个例子演示了如何向一个已经存在着的SerDe增加新的SERDEPROPERTIES属性:

ALTER TABLE table using_JSON_storage SET SERDEPROPERTIES ('prop3'='value3','prop4'='value4');

我们可以修改在“创建表”中讲到的存储属性:

ALTER TABLE stocks CLUSTERED BY (exchange,symbol) SORTED BY(symbol) INTO 48 BUCKETS;

SORTED BY子句是可选的,但是CLUSTER BY和INTO ... BUCKETS子句是必选的。

6.8、众多的修改表语句

在后面“执行钩子”中,我们将讨论一种为各种操作增加执行“钩子”的技巧。
ALTER TABLE ... TOUCH语句用于触发这些钩子:

ALTER TABLE log_messages TOUCH PARTITION(year=2012,month=1,day=1);

PARTITION子句用于分区表。这种语句的一个典型应用场景是,当表中存储的文件在Hive之外被修改了,就会触发钩子的执行。例如,某个脚本往分区2011/01中写人了新的日志信息文件,可以在Hive CLI中进行下面的调用:

hive -e 'ALTER TABLE log_messages TOUCH PARTITION(year=2012,month=1,day=1);

如果表或者分区并不存在,那么这个语句也不会创建表或者分区。在这种情况下要使用合适的创建策略。
ALTER TABLE ... ARCHIVE PARTITION语句会将这个分区内的文件打成一个Hadoop压缩包(HAR)文件。但是这样仅仅可以降低文件系统中的文件数以及减轻NameNode的压力,而不会减少任何的存储空间(例如,通过压缩):

ALTER TABLE log_messages ARCHIVE PARTITION(year=2012,month=1,day=1);

使用UNARCHIVE替换ARCHIVE就可以反向操作。这个功能只能用于分区表中独立的分区。
最后,Hive提供了各种保护。下面的语句可以分别防止分区被删除和被查询:

ALTER TABLE log_messages PARTITION (year=2012,month=1,day=1) ENABLE NO_DROP;
ALTER TABLE log_messages PARTITION (year=2012,month=1,day=1) ENABLE OFFLINE;

使用ENABLE替换DISABLE可以达到反向操作的目的。这些操作也都不可用于非分区表。

猜你喜欢

转载自blog.csdn.net/shenchaohao12321/article/details/82560449