Hive是一個基於Hadoop的數據倉庫基礎架構。Hadoop在通用硬件上對數據存儲和加工(使用map-reduce編程範式)提供巨大的擴張和容錯能力。html
Hive被設計用於簡化海量數據的數據彙總,即席查詢和分析。它提供了一個簡單的查詢語言叫作Hive QL,其基於SQL和使熟悉SQL的用戶容易作即席查詢,彙總和數據分析。同時,Hive QL也容許傳統的map/reduce程序員插入他們自定義的mappers和reducers來作沒有在語言中提供支持的更復雜的分析。java
Hadoop是一個批量處理系統,Hadoop做業傾向於有高延遲和至關大的開銷在做業提交和調度上。所以Hive查詢的延遲一般很高(幾分鐘)即便數據集很是小(如幾百兆)。因此其不能與實施在小數據集的屢次迭代處理且迭代間的響應時間少於幾分鐘的Oracle系統相比較。Hive對準提供可接受(但不是最佳的)延遲於交互數據瀏覽,小數據查詢或測試查詢。node
Hive並不是設計用於在線事務處理和不提供實時查詢和行級更新。其最好用於在大量不可變數據集(像web日誌)上的批量做業。程序員
在接下來的章節咱們提供一個該系統功能的指南。咱們開始於數據類型、表和分區(與你在傳統關係型數據庫找到的很類似)概念的描述和使用一些例子來舉例說明QL語言的功能。web
按尺寸的順序-Hive數據組織成:正則表達式
Databases: 命名空間來隔離表和其餘數據單元的命名衝突。數據庫
Tables:一樣單元的數據擁有一致的schema。一個page_views表的例子,每行可能包含如下字段(schema):apache
timestamp - 是一個INT類型,頁面被訪問的時間,相似於unix timestamp。編程
userid - 是一個BIGINT類型,標識訪問頁面的用戶。json
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文件。
在上面的例子,用戶知道插入哪個分區且一個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.如:咱們假象有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;
用戶能夠在數據流插入他們自定義的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;
在用戶共同使用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);