Hive Tutorial
概念
Hive是什么
Hive是一个基于Hadoop的数据仓库基础架构。Hadoop在通用硬件上对数据存储和加工(使用map-reduce编程范式)提供巨大的扩张和容错能力。
Hive被设计用于简化海量数据的数据汇总,即席查询和分析。它提供了一个简单的查询语言叫做Hive QL,其基于SQL和使熟悉SQL的用户容易做即席查询,汇总和数据分析。同时,Hive QL也允许传统的map/reduce程序员插入他们自定义的mappers和reducers来做没有在语言中提供支持的更复杂的分析。
Hive不是什么
Hadoop是一个批量处理系统,Hadoop作业倾向于有高延迟和相当大的开销在作业提交和调度上。因此Hive查询的延迟通常很高(几分钟)即使数据集非常小(如几百兆)。所以其不能与实施在小数据集的多次迭代处理且迭代间的响应时间少于几分钟的Oracle系统相比较。Hive对准提供可接受(但不是最佳的)延迟于交互数据浏览,小数据查询或测试查询。
Hive并非设计用于在线事务处理和不提供实时查询和行级更新。其最好用于在大量不可变数据集(像web日志)上的批量作业。
在接下来的章节我们提供一个该系统功能的指南。我们开始于数据类型、表和分区(与你在传统关系型数据库找到的很相似)概念的描述和使用一些例子来举例说明QL语言的功能。
数据单元
按尺寸的顺序-Hive数据组织成:
Databases: 命名空间来隔离表和其他数据单元的命名冲突。
Tables:同样单元的数据拥有一致的schema。一个page_views表的例子,每行可能包含以下字段(schema):
timestamp - 是一个INT类型,页面被访问的时间,类似于unix timestamp。
userid - 是一个BIGINT类型,标识访问页面的用户。
page_url - 是一个STRING类型,抓取页面的地址
referer_url - 是一个STRING。抓取引用页面的地址。
IP - 是一个STRING,请求页面的IP地址。
Partitions: 每个表可以有一个或多个分区键来确定数据存储。分区-是一个存储单元-同样允许用户高效识别满足确定条件的行。如:一个STRING类型的date分区和STRING类型的country分区。分区键的每个唯一值指定表的一个分区。如:所有“2009-12-23”的“US”数据是page_views表的一个分区。因此,如果你只在”2009-12-23”的“US”的数据做分析,你可以只在表相关的分区执行查询,因此可以显著提高分析的速度。注意:即使一个分区名字是2009-12-23,不意味着它包含该天所有或唯一的数据;分区命名为日期只是为了方便。保证分区名和数据内容的一致,这是用户的工作。分区字段是虚拟字段,他们不是数据本身的一部分,但是它们来自于装载数据的时候。
Buckets(或Clusters):各个分区的数据可以基于表的一些字段的值用哈希函数拆分到Buckets。如: page_views表可以用userid分到桶。这是page_views表的一个字段,不同于分区字段。这可被用于高效生成数据的样本。
类型系统
Hive提供了原始和复杂数据类型,在下面描述,更多信息见Hive Data Types。
原始类型
类型是与表的字段相关的。下面是支持的原始类型: Integers * TINYINT -1 byte integer * SMALLINT - 2 byte integer * INT - 4 byte integer * BIGINT - 8 byte integer Boolean type * BOOLEAN - TRUE/FALSE Floating point numbers 浮点数 * FLOAT - single precision * DOUBLE- Double precision * String type * STRING - 特定字符集合的字符序列
类型按下面的层级关系组织(父级是所有子实例的超类型)(注:这个层级关系看原文档,看着有点乱):
Type
Number
FLOAT
STRING
INT
TINYINT
SMALLINT
BIGINT
DOUBLE
Primitive Type
BOOLEAN
这个类型层级定义查询语言中的隐含类型转换,隐含类型转换允许类型由子级到祖先的转换。因此当一个查询表达式期望type1但是数据是type2,在类型层级当type1是type2的祖先时,type2隐含转换到type1。注意:类型层级允许STRING到DOUBLE的隐含转换。
直接类型转换可以使用下面展示在#内部函数章节的cast操作符。
复杂类型
复杂类型可以由原始类型和其他复杂类型组合得到: * Structs:该类型中的元素可以通过点号(.)访问。如:STRUCT{a INT; b INT}类型的字段c,a元素可以通过表达式c.a访问。 * Maps(键-值元组):元素通过[‘element name’]符号来访问。如:在一个map M包含一个’group’->gid的映射,gid的值可以通过M[‘group’]访问。 * Arrays(索引列表): array中的元素必须是相同的类型。元素可以通过符号[n]访问n是一个array的索引(zero-based).如:一个array A有元素[‘a’,’b’,’c’],A[1]返回’b’.
使用原始类型和构造来创建复杂类型。可以创造任意内嵌级别的类型。如:User类型包含下面的2个域: * gender- STRING类型。 * active - 是一个BOOLEAN。
内建运算符和函数
下面列举的运算符和函数不是最新的。(更多信息见Hive操作符和UDFS) 在CLI,用一下命令见最新的文档:
SHOW FUNCTIONS;
DESCRIBE FUNCTION <function_name>;
DESCRIBE FUNCTION EXTENDED <function_name>;
[!!] 大小写不敏感 所有Hive的关键字是大小写不敏感的,包括Hive运算符和函数的名字。
内建运算符
*关系运算符 - 下面的运算符比较运算对象并且产生一个TRUE或FALSE值
关系运算符 | 运算对象类型 | 说明 |
---|---|---|
A=B | 所有原始类型 | 如果A等于B为TRUE,否则FALSE。 |
A!=B | 所有原始对象 | 如果A不等于B为TRUE,否则FALSE。 |
A<B | 所有原始对象 | 如果A小于B返回TRUE,否则FALSE。 |
A<=B | 所有原始对象 | 如果A小于等于B返回TRUE,否则FALSE。 |
A>B | 所有原始对象 | 如果A大于B返回TRUE,否则FALSE。 |
A>=B | 所有原始对象 | 如果A大于等于B返回TRUE,否则FALSE。 |
A IS NULL | 所有对象 | 如果A的值为NULL为TRUE,否则FALSE。 |
A IS NOT NULL | 所有对象 | 如果A的值为NULL为FALSE,否则TRUE |
A LIKE B | 字符串 | 如果A匹配SQL简单正则表达式B为TRUE,否则FALSE。这个比较是按一个个字符来。B中的”_”字符匹配A中的任意一个字符(类似于posix正则表达式中的”.”), B中的”%”字符匹配A中任意数量的字符(类似于posix正则表达式中的”.*”).如,’foobar’ LIKE ‘foo’ 值为FALSE,’foobar’ LIKE ‘foo___’和’foobar’ LIKE ‘foo%’的值为TRUE。用\来转义%(%匹配一个%字符)。如果数据中包含分号,而你想查找,需要进行转义,columnValue LIKE ’a\;b’ |
A RLIKE B | 字符串 | 如果A或者B为NULL,结果为NULL,如果A中任意(可能为空)子字符串匹配Java正则表达式B(见Java正则表达式语法)为TRUE,否则为FALSE。如:’foobar’ rlike ‘foo’结果为TRUE,同样’foobar’ rlike ‘^f.*r$’也是 |
A REGEXP B | 字符串 | 和RLIKE一样 |
算术运算符 - 下面的运算符提供在运算对象上的各种常见的算术运算。他们均返回数字类型。
算术运算符 | 运算对象类型 | 说明 |
---|---|---|
A+B | 所有数字类型 | A加B。结果的类型是运算对象的共同父类型(在类型层级中)。如:任意integer是一个float,因此float包含类型integer,所以一个float和一个int应用+操作符返回结果为float. |
A-B | 所有数字类型 | A减B。结果的类型是所有运算对象类型的共同父类型(在类型层级中) |
A*B | 所有数字类型 | A乘B。结果的类型是所有运算对象类型的共同父类型(在类型层级中),注意如果乘法导致溢出,你需要转换其中一个运算对象到一个在类型层级中更高的类型。 |
A/B | 所有数字类型 | A除B。结果的类型是所有运算对象类型的共同父类型(在类型层级中),如果运算对象都是integer类型,结果是除法的商 |
A%B | 所有数字类型 | A除B取余。结果的类型是所有运算对象类型的共同父类型(在类型层级中) |
A&B | 所有数字类型 | A和B按位与。结果的类型是所有运算对象类型的共同父类型(在类型层级中) |
A | B | 所有数字类型 |
A^B | 所有数字类型 | A和B按位异或。结果的类型是所有运算对象类型的共同父类型(在类型层级中) |
~A | 所有数字类型 | A按位取非。结果的类型与A一致 |
逻辑运算符 - 下面的运算符对创建逻辑表达式提供支持。他们依赖于运算对象的boolean值返回boolean TRUE或者FALSE。
逻辑运算符 | 运算对象类型 | 说明 |
---|---|---|
A AND B | boolean | 如果A和B都是TRUE返回TRUE,否则FALSE。 |
A && B | boolean | 和A AND B一致 |
A OR B | boolean | 如果A或者B或者都是TRUE,返回TRUE,否则FALSE |
A | B | |
NOT A | boolean | 如果A为FALSE返回TRUE,否则FALSE |
!A | boolean | 和NOT A一致 |
复杂类型运算符
运算符 | 运算对象类型 | 说明 |
---|---|---|
A[n] | A是一个Array,n是一个int | 返回数组A中的第n个对象,从0开始。如:A为[‘foo’,’bar’]时,A[0]返回’foo’ A[1]返回’bar’ |
M[key] | M是一个Map key的类型为K | 返回map中相应key的值。如:M为{‘f’->’foo’,’b’->’bar’,’all’->’foobar’},M[‘all’]返回’foobar’ |
S.x | S是一个struct | 返回S中的域x,如结构体foobar{int foo, int bar} foobar.foo返回结构体foo域中存储的integer |
内建函数
Hive提供以下内建函数:函数列表见source code:FunctionRegistry.java
返回类型 | 函数名 | 描述 |
---|---|---|
BIGINT | round(double a) | 对double类型取整,返回BIGINT |
BIGINT | floor(double a) | 返回一个小于等于这个double的最大BIGINT值 |
BIGINT | ceil(double a) | 返回一个大于等于这个double的最小BIGINT值 |
double | rand(),rand(int seed) | 返回一个随机数(每行均会变)。指明的seed来决定产生的随机数的范围 |
int | size(Map) | 返回map类型中元素的个数 |
int | size(Array) | 返回array类型中的元素的个数 |
int | year(string date) | 返回一个日期或时间戳字符串中年的部分:year(“1970-01-01 00:00:00”)=1970,year(“1970-01-01”)=1970 |
int | month(string date) | 返回一个日期或时间戳字符串中月的部分:month(“1970-01-01 00:00:00”)=1,month(“1970-01-01”)=1 |
int | day(string date) | 返回一个日期或时间戳字符串中天的部分:day(“1970-01-01 00:00:00”)=1,day(“1970-01-01”)=1 |
string | concat(string A,string B,…) | 返回一个字符串,由A连接B。如:concat(‘foo’,’bar’)结果为’foobar’,这个函数接受任意多个参数,然后返回连接他们的字符串。 |
string | substr(string A,int start) | 返回A的子字符串,从start的位置到字符串结束。如:substr(‘foobar’,4)=’bar’ |
string | substr(string A,int start,int length) | 返回A的子字符串,从start位置开始取指定长度。如:substr(‘foobar’,4,2)=’ba’ |
string | upper(string A) | 返回将所有A的字符转为大写后的字符串,如:upper(‘fOoBaR’)=’FOOBAR’ |
string | ucase(string A) | 和upper一样 |
string | lower(string A) | 返回将所有A的字符转为小写后的字符串,如:lower(‘fOoBaR’)=’foobar’ |
string | lcase(string A) | 和lower一样 |
string | trim(string A) | 返回去掉A开始和结尾空格后的字符串,如:trim(‘foobar ‘)=’foobar’ |
string | ltrim(string A) | 返回去掉A开始(左边)空格后的字符串,如:ltrim(‘ foobar’)=’foobar’ |
string | rtrim(string A) | 返回去掉A结尾(右边)空格后的字符串,如:rtrim(‘foobar ‘)=’foobar’ |
string | regexp_replace(string A,string B,string C) | 返回将A中所有匹配B的Java正则表达式语法(见Java正则表达式语法)的替换为C。如:regexp_replace(‘foobar’,’oo |
string | from_unixtime(int unixtime) | 转换从unix纪元(1970-01-01 00:00:00 UTC)的秒数为一个在当前系统时区的当时的时间戳字符串,格式为”1970-01-01 00:00:00” |
string | to_date(string timestamp) | 返回时间戳字符串的日期部分:to_date(“1970-01-01 00:00:00”)=”1970-01-01” |
string | get_json_object(string json_string, string path) | 在指定的json path的json字符串提取json对象,返回一个提取json对象的json字符串。如果输入json字符串不合法返回null |
value of | cast( as ) | 转换表达式expr的结果类型为.如:cast(‘1’ as BIGINT)转换字符串‘1’为其整数形式,如果转换失败返回null |
下面是Hive中内建的聚集函数
返回类型 | 聚集函数名 | 说明 |
---|---|---|
BIGINT | count(*),count(expr),count(DISTINCT expr[,expr_.]) | count(*)-返回取回行的记录条数,包括包含NULL值的行;count(expr)-返回表达式不为NULL的记录条数;count(DISTINCT expr[,expr])-返回指定表达式不为空且去重后的记录条数。 |
DOUBLE | sum(col),sum(DISTINCT col) | 返回分组中字段或分组中字段不同值的和 |
DOUBLE | avg(col),avg(DISTINCT col) | 返回分组中字段或分组重字段不同值的平均值 |
DOUBLE | min(col) | 返回分组中字段的最小值 |
DOUBLE | max(col) | 返回分组中字段的最大值 |
语言能力
Hive查询语言提供了基础SQL类似运算符。这些运算符可以使用在表和分区上。这些运算符为:
使用where子句从表过滤行
使用select子句从表选取指定字段
在2个表之间做等值连接
在表存储的数据中执行聚合函数在多个”group by”字段上。
存储查询结果到另一张表
导出一个表的内容到一个本地目录(如nfs)
存储查询结果到hadoop的dfs目录。
管理表和分区(create,drop and alter)
选择插入自定义脚本到语言中来自定义map/reduce作业
使用例子
下面的例子显示了系统中的主要特性,更详细的查询测试案例见Hive查询测试案例和对应的结果在测试案例查询结果
创建,展示,变更和删除表
关于创建,展示,变更和删除表的详细信息见Hive数据定义语言
创建表
创建上面提到的page_view表的例子语句如下:
CREATE TABLE page_view(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
COMMENT 'This is the page view table'
PARTITIONED BY(dt STRING, country STRING)
STORED AS SEQUENCEFILE;
在这个例子中,表的字段都指定了相应的类型。注释可以同时跟随在字段级和表级。另外分区子句定义了与数据字段不同的分区字段且实际上不和数据存储在一起。按上述指定,文件中的数据的字段分隔符为ASCII 001(ctrl-A)和换行符为行分隔。
如果数据不是按上述格式的,字段分隔符可以作为参数,见下面的例子:
CREATE TABLE page_view(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
COMMENT 'This is the page view table'
PARTITIONED BY(dt STRING, country STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '1'
STORED AS SEQUENCEFILE;
行分隔符目前不能改变,因为他不是由Hive而是由Hadoop决定的。
同样是个好主意去对表在指定的列上做bucket,来提高对数据集做抽样查询的效率。如果没有bucketing,也可以在表上做随机抽样,但是不是高效的由于要扫描所有的数据。下面的举例说明表page_view根据列userid来做bucketed。
CREATE TABLE page_view(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
COMMENT 'This is the page view table'
PARTITIONED BY(dt STRING, country STRING)
CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '1'
COLLECTION ITEMS TERMINATED BY '2'
MAP KEYS TERMINATED BY '3'
STORED AS SEQUENCEFILE;
在这个例子中,表的字段都指定了相应的类型。注释可以同时跟随在字段级和表级。另外分区子句定义了与数据字段不同的分区字段且实际上不和数据存储在一起。CLUSTERED BY子句指定哪一个列用于bucketing还有创建多少个桶。定界行格式指定行如何存储在hive的表中。在这个例子的分隔格式,指定了字段如何结尾,集合(数组或maps)中的项如何结尾和map的键如何结尾。STORED AS SEQUENCEFILE指定数据存储为二进制格式(使用hadoop序列文件)在hdfs上。在上面的例子中ROW FORMAT和STORED AS子句的值展示了系统的默认值。
表名和列名是大小写不敏感的。
浏览表和分区
SHOW TABLES;
有超出你希望的方式来列举仓库中存在的表。
SHOW TABLES 'page.*';
列举以’page’作前缀的表。这个模式遵循Java正则表达式的语法(点号是个通配符)。
SHOW PARTITIONS page_view;
列举表的分区。如果表不是分区表将会报错。
DESCRIBE page_view;
列举表的列和列类型
DESCRIBE EXTENDED page_view;
列举表的列和所有的其他属性。他会打印很多的信息并且不是很美观的输出,通常用于调试。
DESCRIBE EXTENDED page_view PARTITION (ds='2008-08-08');
列举分区的列和所有的其他属性。这个也会打印很多信息通常用于调试。
变更表
修改一个存在的表为一个新名字。如果新表名已经存在,将返回一个错误。
ALTER TABLE old_table_name RENAME TO new_table_name;
重命名表的列。确保使用同样的列类型和包括所有已经存在的列:
ALTER TABLE old_table_name REPLACE COLUMNS(col1 TYPE,...);
表增加字段
ALTER TABLE tab1 ADD COLUMNS(c1 INT COMMENT 'a new int column', c2 STRING DEFAULT 'def val');
注意:变更表定义(如增加列),当表是分区表是保存旧分区的表定义。所有的访问旧分区的列的查询,对于那些列隐含的返回一个null值或者一个特殊的默认值
在以后的版本,当列在特定分区的配置中没有找到时的行为不是某个确定的值,而是抛出一个错误。
删除表和分区
删除表是相当平常的。删除表隐含的会删除该表上的索引(这是将来的特性)。相关的命令:
DROP TABLE pv_users;
删除一个分区。变更表删除一个分区。
ALTER TABLE pv_users DROP PARTITION (ds='2008-08-08')
注意该表或者分区上的所有数据会被删除且不能恢复。*
装载数据
有多种方法来装载数据到Hive的表。用户可以创建一个外部表指向HDFS上的一个指定的位置。在这个特别的用法,用户可以复制一个文件到一个指定的路径通过使用HDFS的put或copy命令并创建一个表指向这个位置和所有的相关的行格式信息。一旦这个做好了,用户可以转换这些数据并插入任意其他的Hive表。如:文件/tmp/pv_2008-06-08.txt包含2008-06-08的逗号分隔页面访问服务信息。这些需要装载到page_view表的对应的分区。下面的连续的命令可以做到:
CREATE EXTERNAL TABLE page_view_stg(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User',
country STRING COMMENT 'country of origination')
COMMENT 'This is the staging page view table'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '44' LINES TERMINATED BY '12'
STORED AS TEXTFILE
LOCATION '/user/data/staging/page_view';
hadoop dfs -put /tmp/pv_2008-06-08.txt /user/data/stage/page_view
FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country='US')
SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip
WHERE pvs.country='US';
在上面的例子中,对目标表的array和map类型均插入null值。如果指定合适的行格式后,那写也可以从外部表取到。
这个方法在数据已经存在HDFS上,用户只需提供一些元数据信息,那这些数据就可以通过Hive来查询和操作了。
另外 系统也提供了语法可以从本地文件系统的文件中直接装载数据到Hive表,需要输入数据格式与表的格式一致。如果/tmp/pv_2008-06-08_us.txt已经包含了US的数据,这样我们不需要像上面的例子附加过滤。这个例子的夹在可以使用下面的语法:
LOAD DATA LOCAL INPATH /tmp/pv_2008-06-08_us.txt INTO TABLE page_view PARTITION(date='2008-06-08', country='US')
路径参数可以为目录(这样该目录下的所有文件均会加载),单一文件名或者通配符(这样所有匹配的文件均会加载)。如果参数是一个目录-其不能包含子目录。同样的-通配符只能匹配文件名。
这个例子中,输入文件/tmp/pv_2008-06-08_us.txt非常大,用户期望并行加载这个数据(使用Hive之外的工具)。当文件在HDFS后,下面的语法可以用于加载数据到Hive的表:
LOAD DATA INPATH '/user/data/pv_2008-06-08_us.txt INTO TABLE page_view PARTITION(date='2008-06-08', country='US')
上面的例子假设input.txt文件中的array和map字段都是null字段。 装载数据到Hive表的更多信息见Hive Data Manipulation Language
查询和插入数据
Hive查询操作见文档Select,插入操作见文档Inserting data into Hive Tables from queries和Writing data into the filesystem from queries
简单查询
所有的活跃用户,可以使用下面的查询:
INSERT OVERWRITE TABLE user_active
SELECT user.*
FROM user
WHERE user.active=1;
注意:不像SQL,我们总是插入结果到表。后面我们会举例说明,用户怎么检查结果并导出到一个本地文件。你也可以在Hive CLI运行下面的查询:
SELECT user.*
FROM user
WHERE user.active=1;
这个会在内部重写到一些临时文件,然后显示到Hive客户端。
分区基础查询
查询中使用到哪个分区是有系统基于where子句的条件中的分区列自动确定的。如:取得2008/03月的所有的page_views从域xyz.com链接来的,可以写下面的查询:
INSERT OVERWRITE TABLE xyz_com_page_views
SELECT page_views.*
FROM page_views
WHERE page_views.date>='2008-03-01' AND page_views.date<='2008-03-31' AND
page_views.referrer_url like '%xyz.com';
注意:page_views.date在这里使用是因为上面的表定义了PARTITIONED BY(date DATETIME, country STRING);如果你命名分区为不同的名字,不要期望.date符合你的想法工作。
关联
取得2008-03-03的page_view的用户信息,需要通过userid关联page_view表和user表。这个可以通过join来完成,见下面的查询:
INSERT OVERWRITE TABLE pv_users
SELECT pv.*, u.gender, u.age
FROM user u JOIN page_view pv ON (pv.userid = u.id)
WHERE pv.date='2008-03-03';
外连接用户可以使用LEFT OUTER, RIGHT OUTER或者FULL OUTER关键字来指定外连接的方式(保留左边,保留右边或者两者都保留)。如:上面的查询使用全连接(full outer join),对应的语法看起来像下面的查询:
INSERT OVERWRITE TABLE pv_users
SELECT pv.*, u.gender, u.age
FROM user u FULL OUTER JOIN page_view pv on (pv.userid = u.id)
WHERE pv.date='2008-03-03';
检查另一个表是否存在键,用户可以用LEFT SEMI JOIN举例说明如下:
INSERT OVERWRITE TABLE pv_users
SELECT u.*
FROM user u LEFT SEMI JOIN page_view pv on (pv.userid=u.id)
WHERE pv.date='2008-03-03';
连接超过一张表,用户可以使用以下语法:
INSERT OVERWRITE TABLE pv_friends
SELECT pv.*, u.gender, u.age, f.friends
FROM page_view pv JOIN user u on (pv.userid=u.id) JOIN friend_list ON (u.id = f.uid)
WHERE pv.date='2008-03-03';
注意:Hive只支持等值连接equi-joins。另在关联中最好将最大的表放在最右边来获得最高的性能。
聚合
根据性别汇总用户数量,可以使用以下查询:
INSERT OVERWRITE TABLE pv_gender_sum
SELECT pv_users.gender, count(DISTINCT pv_users.userid)
FROM pv_users
GROUP BY pv_users.gender;
同时可以使用多个聚合,但是不能2个聚合使用不同的DISTINCT列。如:下面的是允许的:
INSERT OVERWRITE TABLE pv_gender_agg
SELECT pv_users.gender, count(DISTINCT pv_users.userid), count(*), sum(DISTINCT pv_users.userid)
FROM pv_users
GROUP BY pv_users.gender;
然而,接下来的查询是不允许的:
INSERT OVERWRITE TABLE pv_gender_agg
SELECT pv_users.gender, count(DISTINCT pv_user.userid), count(DISTINCT pv_users.ip)
FROM pv_users
GROUP BY pv_users.gender;
多表/文件插入
聚合或简单查询的输出可以插入多张表或是hadoop的dfs文件(通过hdfs工具来操作).如:一个按性别分组,另一个按年龄分组统计page views。可以使用以下查询:
FROM pv_users
INSERT OVERWRITE TABLE pv_gender_sum
SELECT pv_users.gender, count_distinct(pv_users.userid)
GROUP BY pv_users.gender
INSERT OVERWRITE DIRECTORY '/user/data/tmp/pv_age_sum'
SELECT pv_users.age, count_distinct(pv_users.userid)
GROUP BY pv_users.age;
第一个insert子句发送第一个分组的结果到Hive的表,第二个发送结果到hadoop dfs文件。
动态分区插入Dynamic-partition insert
在上面的例子,用户知道插入哪一个分区且一个insert语句只能插入一个分区。如果你需要装载数据到多个分区,你需要使用multi-insert语句举例如下。
FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country='US')
SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip WHERE pvs.country = 'US'
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country='CA')
SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip WHERE pvs.country = 'CA'
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08', country='UK')
SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip WHERE pvs.country = 'UK';
装载一天的数据到所有国家分区,你需要对每一个国家增加一个insert语句。这是非常不方便的,你需要事先知道输入数据中存在的所有的国家信息并预先创建分区。如果这个列表在另一天改变了。你需要修改你的insert DML还有分区创建的DDLs。这也是低效的当每个插入语句转换为一个MapReduce作业。
动态分区插入(或者多分区插入multi-partition insert)设计来解决这个问题,通过扫描输入的表动态确定哪个分区需要创建和存放数据。这是一个新特性从0.6.0版本开始支持。在动态分区插入,输入列的值来确定该行被插入哪个分区。如果那个分区没有被创建,将会自动创建那个分区。使用这个特性你只需要一个insert语句来创建和插入需要的分区。另外这样只有一个insert语句,这样只有一个对应的MapReduce作业。相比较多个插入的例子这显著提高了效率和减少了Hadoop集群负载。
下面是一个装载数据到所有国家分区只使用一个insert语句的例子:
FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt='2008-06-08',country)
SELECT pvs.viewTime,pvs.userid,pvs.page_url,pvs.referrer_url,null,null,pvs.ip,pvs.country
相比于多个插入的语句这有几个不同的语法:
country出现在PARTITION说明,但是没有对应的值。在这个例子中,country是一个动态分区列,令一方面,ds列有指定值,这是一个静态分区列。如果一个列是动态分区列,他的值从输入列获取。当前,我们只允许在分区子句中动态分区列为最后的列。因为分区列的次序指定了其分层的次序(意思是dt是主分区,country是子分区).你不能指定一个分区子句使用(dt,country=’US’).因为那意味着你需要更新其country子分区为’US’的所有日期的分区。
select语句中增加了一个pvs.country列。这是一个动态分区列对应的输入列。注意:你不需要对静态分区列增加一个输入列因为他的值在分区子句已经知道了。注意:动态分区值是按次序不是名字。选取select子句的结尾的列s。
动态分区insert语句的语义:
如果已经有动态分区列对应的非空分区存在(如:country=’CA’在某些日期主分区存在)。如果动态分区insert在输入数据中发现同样的值(‘CA’)其将会被覆盖写入。这个是’insert overwrite’的语义。然而,如果分区值’CA’不存在与输入数据中,这个存在的分区将不会被覆盖写入。
Hive的分区对应于HDFS的一个目录。分区值需要与HDFS的路径格式(URI in Java)相符.在URI中任意有特殊含义的字符(如:’%’,:’,’/’,’#’)将会用’%’接着其ASCII的2字节的值转义。
如果输入列的类型不是STRING,其值将先转换为STRING再用于构造HDFS路径。
如果输入列值为NULL或者空字符串。该行将会仿如到一个特殊的分区。这个名字有hive参数hive.exec.default.partition.name控制。默认值为HIVE_DEFAULT_PARTITION{}.基本上这个分区包含所有那些值不是有效分区名的”坏”行。该方案需要注意的是,不合法的值将会丢失然后替换为HIVE_DEFAULT_PARTITION{}当你在Hive中查询的时候。JIRA HIVE-1309是一个解决方案让用户定义”bad file”来保留输入分区列的值。
动态分区插入可能占用大量资源,因为其需要生成大量的分区在很短的时间。获取控制,我们定了了3个参数:
hive.exec.max.dynamic.partitions.pernode(默认值100)每个mapper或reducer可以创建的最大动态分区数。如果一个mapper或者reducer创建多于这个阀值,一个重大错误将会被从mapper/reducer抛出且这个作业被杀掉。
hive.exec.max.dynamic.partitions(默认值100)一个DML语句可以创建的总的动态分区数。如果每个mapper/reducer没有超过限制,但是总动态分区数超过了,在作业的最后在中间数据移动到最后的目标之前一个异常将会抛出。
hive.exec.max.created.files(默认值100000)所有的mapper和reducer创建的最大文件总数。这个是由每个mapper/reducer创建文件时更新Hadoop的counter实现的。如果总数超过hive.exec.max.created.files,一个重大错误被抛出作业被杀掉。
另一种情形,我们希望保护用户意外定义所有分区为动态分区,没有指定一个静态分区。而其原本的目的是只覆盖一个主分区的子分区。我们定义令一个参数hive.exec.dynamic.partition.mode=strict来预防全部动态分区的情况。在strict模式,你需要指定最少一个静态分区。默认模式为strict。另外,我们有一个参数 hive.exec.dynamic.partition=true/false来控制是否允许全部动态分区,默认值为false。
在Hive0.6,动态分区插入不能与hive.merge.mapfiles=true或hive.merge.mapredfiles=true一起工作。所以其内部关闭了merge参数。合并文件在动态分区插入在Hive0.7版支持(见JIRA HIVE-1307)
故障处理和最佳实践
在上面的阐述,在那里有很多的动态分区被特定的mapper/reducer撞见。一个重大错误将抛出且作业被杀掉。 这个错误信息看起来像:
hive> set hive.exec.dynamic.partition.mode=nonstrict; hive> FROM page_view_stg pvs INSERT OVERWRITE TABLE page_view PARTITION(dt, country) SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip, from_unixtimestamp(pvs.viewTime, ‘yyyy-MM-dd’) ds, pvs.country; … 2010-05-07 11:10:19,816 Stage-1 map = 0%, reduce = 0% [Fatal Error] Operator FS_28 (id=41): fatal error. Killing the job. Ended Job = job_201005052204_28178 with errors …
这个问题是一个mapper获得了随机集合的多行,其唯一的(dt,country)对超过了hive.exec.max.dynamic.partitions.pernode的限制。一个解决方法是,聚合mapper中动态分区列的行,然后分发到多个reducer在那里去创建动态分区。在这个例子中不同的动态分区的数量将明显减少。上面例子的查询可以重写为:
hive> set hive.exec.dynamic.partition.mode=nonstrict;
hive> FROM page_view_stg pvs
INSERT OVERWRITE TABLE page_view PARTITION(dt, country)
SELECT pvs.viewTime, pvs.userid, pvs.page_url, pvs.referrer_url, null, null, pvs.ip,
from_unixtimestamp(pvs.viewTime, 'yyyy-MM-dd') ds, pvs.country
DISTRIBUTE BY ds, country;
这个查询将生成一个MapReduce作业而不是一个Map-only作业。SELECT子句将转换为一个mappers然后输出基于(ds,country)pairs的值,分布到多个reducer。INSERT子句转换为在reducer写到动态分区。
更多文档:
插入本地文件
在确定的情形你希望输出写到本地文件,从而可以导入到excel电子表格。这个可以通过以下命令完成:
INSERT OVERWRITE LOCAL DIRECTORY '/tmp/pv_gender_sum'
SELECT pv_gender_sum.*
FROM pv_gender_sum;
抽样
抽样子句允许用户写一个查询抽样数据作为替代查询整张表。现在抽样是在CREATE TABLE语句中CLUSTERED BY子句中定义的列来完成的。在下面的例子中我们选择了pv_gender_sum表32个buckets中的第3个桶
INSERT OVERWRITE TABLE pv_gender_sum_sample
SELECT pv_gender_sum.*
FROM pv_gender_sum TABLESAMPLE(BUCKET 3 OUT OF 32);
一般的TABLESAMPLE语法看起来如下:
TABLESAMPLE(BUCKET x OUT OF y)
y是该表创建时定义的buckets数量的倍数或者公约数。如果bucket_number对y取模等于x就选择该bucket。因此上面的例子在下面的tablesample子句将挑选第3个和第19个bucket。
TABLESAMPLE(BUCKET 3 OUT OF 16)
buckets的编号从0开始。
另一面 下面的tablesample子句将挑选第3个bucket的一半
TABLESAMPLE(BUCKET 3 OUT OF 64 ON userid)
Union All
这个语言也提供了union all.如:我们假象有2张不同的表一个用户发布了视频另一个用户发表了评论。下面的查询合并了所有发布的视频和发布的评论然后关联users表,建立一个单独的流。
INSERT OVERWRITE TABLE action_users
SELECT u.id, actions.date
FROM (
SELECT av.uid as uid FROM action_video av
WHERE av.date='2008-06-03'
UNION ALL
SELECT ac.uid AS uid
FROM action_comment ac
WHERE ac.date='2008-06-03'
) actions JOIN users u on (u.id=actions.uid);
数组运算
Array columns in tables can only be created programmatically currently. We will be extending this soon to be available as part of the create table statement. 当前的例子呈现pv.friends的类型为array.这是一个整数数组。用户可以根据其索引得到数组中的特定元素。参见下面的命令:
SELECT pv.friends[2] FROM page_views pv;
select表达式获得pv.friends数组的第3个项。 用户也可以得到数组的长度,通过使用size函数展示如下:
SELECT pv.userid, size(pv.friends)
FROM page_view pv;
映射(联合数组)运算
映射提供集合类是联合数组。Such structures can only be created programmatically currently. We will be extending this soon.当前的例子呈现pv.properties是一个map类型。阿就是其是一个字符串到字符串的联合数组。 相应的,下面的查询:
INSERT OVERWRITE page_views_map
SELECT pv.userid, pv.properties['page type']
FROM page_views pv;
可以用来从page_views表选择’page_type’属性。 类似于数组。size函数也可应用于取得映射中元素的个数。见下面的查询:
SELECT size(pv.properties)
FROM page_view pv;
自定义Map/Reduce脚本
用户可以在数据流插入他们自定义的mappers和reducers通过使用Hive语言中天生支持的特性。例如:运行一个自定义mapper脚本-map_script-和一个自定义reducer脚本-reduce_script-用户可以使用下面的命令通过使用TRANSFORM子句来嵌入mapper和reducer脚本。
注意:那些列将被转换为由TAB分隔的字符串在提供给用户脚本之前。且用户脚本的标准输出处理为TAB分隔的字符串列。用户脚本可以输出调试信息到标准错误输出,这可以显示hadoop上任务的详细信息。
FROM (
FROM pv_users
MAP pv_users.userid, pv_users.date
USING 'map_script'
as dt, uid
CLUSTER BY dt) map_output
INSERT OVERWRITE TABLE pv_users_reduced
REDUCE map_output.dt, map_output.uid
USING 'reduce_script'
AS date,count;
示例map脚本(weekday_mapper.py)
import sys
import datetime
for line in sys.stdin:
line = line.strip()
userid, unixtime = line.split('\t')
weekday = datetime.datetime.fromtimestamp(float(unixtime)).isoweekday()
print ','.join([userid,str(weekday)])
当然,MAP和REDUCE都是一般的select transform的“语法糖”。这个子查询也可以这样写:
SELECT TRANSFORM(pv_users.userid, pv_users.date) USING 'map_script' AS dt, uid CLUSTER BY dt FROM pv_users;
弱模式map/reduce:如果在”USING map_script”之后没有”AS”子句。Hive将呈现这个脚本的输出包含2部分:键在第一个tab之前,和值在第一个tab之后。注意:这个与指定”AS key,value”不同,应为在这个例子值只包含在第一个tab和第二个tab之间,如果有多个tab的话。
这样,我们允许用户迁移旧的map/reduce脚本而不需要知道map输出的格式。用户仍然需要知道reduce输出的格式因为那个需要匹配我们要插入的那个表。
FROM (
FROM pv_users
MAP pv_users.userid, pv_users.date
USING 'map_script'
CLUSTER BY key) map_output
INSERT OVERWRITE TABLE pv_users_reduced REDUCE map_output.dt, map_output.uid USING ‘reduce_script’ AS date, count;
Distribute By和Sort By:替代指定”cluster by”。用户可以指定”distribute by”和”sort by”.从而分区列和排序列可以是不同的。通常的情况分区列是排序列的前面的部分。但是那不是必须的。
FROM (
FROM pv_users
MAP pv_users.userid, pv_users.date
USING 'map_script'
AS c1,c2,c3
DISTRIBUTE BY c2
SORT BY c2, c1) map_output
INSERT OVERWRITE TABLE pv_users_reduced
REDUCE map_output.c1,map_output.c2,map_output.c3
USING 'reduce_script'
AS date,count;
Co-Groups
在用户共同使用map/reduce中,cogroup是一个相当通用的运算在数据从多张表提交到自定义的reducer在表上行俺指定列的值分组。通过UNION ALL与CLUSTER BY,这个可以在Hive查询语言中通过以下方式完成。假定我们需要从cogroup 从表actions_video和action_comments通过uid字段然后提交他们到’reduce_script’自定义reducer,用户可以使用下面的语法:
FROM (
FROM (
FROM action_video av
SELECT av.uid AS uid, av.id AS id, av.date AS date
UNION ALL
FROM action_comments ac
SELECT ac.uid AS uid, ac.id AS id, ac.date AS date
) union_actions
SELECT union_actions.uid, union_actions.id, union_actions.date
CLUSTER BY union_actions.uid) map
INSERT OVERWRITE TABLE actions_reduced
SELECT TRANSFORM(map.uid, map.id, map.date) USING 'reduce_script' AS (uid, id, reduce_val);
来源:oschina
链接:https://my.oschina.net/u/33605/blog/212593