1. 程式人生 > >【Hive】05-HiveQL:資料操作

【Hive】05-HiveQL:資料操作

1、向管理表中裝載資料

既然Hive沒有行級別的資料插人、資料更新和刪除操作,那麼往表中裝載資料的唯一途徑就是使用一種“大量”的資料裝載操作。或者通過其他方式僅僅將檔案寫人到正確的目錄下。
在“分割槽表、管理表”中我們已經看到了一個如何裝載資料到管理表中的例子,這裡我們稍微對其增加些內容重新進行展示。我們新增了一個關鍵字OVERWRITE:

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

如果分割槽目錄不存在的話,這個命令會先建立分割槽目錄,然後再將資料拷貝到該目錄下。

如果目標表是非分割槽表,那麼語句中應該省略PARTITION子句。
通常情況下指定的路徑應該是一個目錄,而不是單個獨立的檔案。Hive會將所有檔案都拷貝到這個目錄中。這使得使用者將更方便地組織資料到多檔案中,同時,在不修改Hive指令碼的前提下修改檔案命名規則。不管怎麼樣,檔案都會被拷貝到目標表路徑下而且檔名會保持不變。
如果使用了LOCAL這個關鍵字,那麼這個路徑應該為本地檔案系統路徑。資料將會被拷貝到目標位置。如果省略掉LOCAL關鍵字,那麼這個路徑應該是分散式檔案系統中的路徑。這種情況下,資料是從這個路徑轉移到目標位置的。

提示:LOAD DATA LOCAL ... 本地資料到位於分散式檔案系統上的目標位置,而LOAD DATA ...(也就是沒有使用LOCAL)轉移資料到目標位置。
之所以會存在這種差異,是因為使用者在分散式檔案系統中可能並不需要重複的多份資料檔案拷貝。
同時,因為檔案是以這種方式移動的,Hive要求原始檔和目標檔案以及目錄應該在同一個檔案系統中。例如,使用者不可以使用LOAD DATA語句將資料從一個叢集的HDFS中轉載(轉移)到另一個叢集的HDFS中。
指定全路徑會具有更好的魯棒性,但也同樣支援相對路徑。當使用本地模式執行時,相對路徑相對的是當Hive CLI啟動時使用者的工作目錄。對於分散式或者偽分散式模式,這個路徑解讀為相對於分散式檔案系統中使用者的根目錄,該目錄在HDFS和MapRFS中預設為/user/$USER。

如果使用者指定了OVERWRITE關鍵字,那麼目標資料夾中之前存在的資料將會被先刪除掉。如果沒有這個關鍵字,僅僅會把新增的檔案增加到目標資料夾中而不會刪除之前的資料。然而,如果目標資料夾中已經存在和裝載的檔案同名的檔案,那麼舊的同名檔案將會被覆蓋重寫。(事實上如沒有使用OVERWRITE關鍵字,而目標資料夾下已經存在同名的檔案時,會保留之前的檔案並且會重新命名新檔案為“之前的檔名序列號”)

如果目標表是分割槽表那麼需要使用PARTITION子句,而且使用者還必須為每個分割槽的鍵指定一個值。
按照之前所說的那個例子,資料現在將會存放到如下這個資料夾中:
hdfs://master-server/user/hive/warehouse/mydb.db/employees/country=US/state=CA
對於INPATH子句中使用的檔案路徑還有一個限制,那就是這個路徑下不可以包含任何資料夾。
Hive並不會驗證使用者裝載的資料和表的模式是否匹配。然而,Hive會驗證檔案格式是否和表結構定義的一致。例如,如果表在建立時定義的儲存格式是SEQUENCEFILE,那麼轉載進去的檔案也應該是sequencefile格式的才行。

2、通過查詢語句向表中插入資料

INSERT語句允許使用者通過查詢語句向目標表中插人資料。依舊使用前章中表employees作為例子,這裡有一個俄亥俄州的例子,這裡事先假設另一張名為staged_employees的表裡己經有相關資料了。在表staged_employees中我們使用不同的名字來表示國家和州,分別稱作cnty和st,這樣做的原因稍後會進行說明。

INSERT OVERWRITE TABLE employees
PARTITION(country='US',state='OR')
SELECT * FROM staged_employees se
WHERE se.cnty='US' AND se.st='OR';

這裡使用了OVERWRITE關鍵字,因此之前分割槽中的內容(如果是非分割槽表,就是之前表中的內容)將會被覆蓋掉。
這裡如果沒有使用OVERWRITE關鍵字或者使用INTO關鍵字替換掉它的話,那麼Hive將會以追加的方式寫人資料而不會覆蓋掉之前已經存在的內容。這個例子展示了這個功能非常有用的一個常見的場景,即:資料已經存在於某個目錄下,對於Hive來說其為一個外部表,而現在想將其導人到最終的分割槽表中。如果使用者想將源表資料導人到一個具有不同記錄格式(例如,具有不同的欄位分割符)的目標表中的話,那麼使用這種方式也是很好的。
然而,如果表staged_employees非常大,而且使用者需要對65個州都執行這些語句,那麼也就意味這需要掃描staged_employees表65次!Hive提供了另一種INSERT語法,可以只掃描一次輸人資料,然後按多種方式進行劃分。如下例子顯示瞭如何為3個州建立表employees分割槽:

FROM staged_employees se
INSERT OVERWRITE TABLE employees PARTITION(country='US',state='OR')
   SELECT * WHERE se.cnty='US' AND se.st='OR'
INSERT OVERWRITE TABLE employees PARTITION(country='US',state='CA')
   SELECT * WHERE se.cnty='US' AND se.st='CA'
INSERT OVERWRITE TABLE employees PARTITION(country='US',state='IL')
   SELECT * WHERE se.cnty='US' AND se.st='IL';

這裡我們使用了縮排使得每組句子看上去更清楚。從staged_employees表中讀取的每條記錄都會經過一條SELECT…WHERE一句子進行判斷。這些句子都是獨立進行判斷的,這不是IF...THEN…ELSE一結構!
事實上,通過使用這個結構,源表中的某些資料可以被寫人目標表的多個分割槽中或者不被寫人任一個分割槽中。
如果某條記錄是滿足某個SELECT…WHERE一語句的話,那麼這條記錄就會被寫人到指定的表和分割槽中。簡單明瞭地說,每個INSERT子句,只要有需要,都可以插人到不同的表中,而那些目標表可以是分割槽表也可以是非分割槽表。
因此,輸人的某些資料可能輸出到多個輸出位置而其他一些資料可能就被刪除掉了!
當然,這裡可以混合使用INSERT OVERWRITE句式和INSERT INTO句式。

動態分割槽插入
前面所說的語法中還是有一個問題,即:如果需要建立非常多的分割槽,那麼使用者就需要寫非常多的SQL!不過幸運的是,Hive提供了一個動態分割槽功能,其可以基於查詢引數推斷出需要建立的分割槽名稱。相比之下,到目前為止我們所看到的都是靜態分割槽。
請看如下對前面例子修改後的句子:

INSERT OVERWRITE TABLE employees
PARTITION (country.state)
SELECT ... ,se.cnty,se.st
FROM staged_employeesse;

Hive根據SELECT語句中最後2列來確定分割槽欄位country和state的值。這就是為什麼在表staged_employees中我們使用了不同的命名,就是為了強調源表字段值和輸出分割槽值之間的關係是根據位置而不是根據命名來匹配的。
假設表staged_employees中共有100個國家和州的話,執行完上面這個查詢後,表employees就將會有100個分割槽!
使用者也可以混合使用動態和靜態分割槽。如下這個例子中指定了country欄位的值為靜態的US,而分割槽欄位state是動態值:

INSERT OVERWRI TETABLE employees
PARTITION(country='US',state)
SELECT ...,se.cnty,se.st
FROM staged_employees se
WHERE se.cnty='US';

靜態分割槽鍵必須出現在動態分割槽鍵之前。
動態分割槽功能預設情況下沒有開啟。開啟後,預設是以“嚴格”模式執行的,在這種模式下要求至少有一列分割槽欄位是靜態的。這有助於阻止因設計錯誤導致查詢產生大量的分割槽。例如,使用者可能錯誤地使用時間戳作為分割槽欄位,然後導致每秒都對應一個分割槽!而使用者也許是期望按照天或者按照小時進行劃分的。還有一些其他相關屬性值用於限制資源利用。下表描述了這些屬性。
 

動態分割槽屬性
屬性名稱 預設值 描述
hive.exec.dynamic.partition false 設定成true,表示開啟動態分割槽功能
hive.exec.dynamic.partition.mode strict 設定成nonstrict,表示允許所有分割槽都是動態的
hive.exec.max.dynamtc.partltions.pernode 100 每個mapper或reducer可以建立的最大動態分割槽個數。如果某個mapper或reducerr嘗試建立大於這個值的分割槽的話則會丟擲一個致命錯誤資訊
hive.exec.max.dynanuc.pattltions +1000 一個動態分割槽建立語句可以建立的最大動態分割槽個數。如果超過這個值則會丟擲一個致命錯誤資訊
hive.exec.maxcreated.files 100000 全域性可以建立的最大檔案個數。有一個Hadoop計數器會跟蹤記錄建立了多少個檔案,如果超過這個值則會丟擲一個致命錯誤資訊

因此,作為例子演示,前面我們使用的第一個使用動態分割槽的例子看上去應該是像下面這個樣子的,這裡我們不過在使用前設定了一些期望的屬性:

hive> set hive.exec.dynamic.partition=true;
hive> set hive.exec.dynamc.partition.mode=nonstrict;
hive> set hive.exec.max.dynamic.pernode=1000;
hive> INSERT OVERWRITE TABLE employees
       > PARTITION(country.state)
       > SELECT ...,se.cnty,se.st
       > FROM staged_employeesse;

3、單個查詢語句中建立表並載入資料

使用者同樣可以在一個語句中完成建立表並將查詢結果載人這個表的操作:

CREATE TABLE ca_employees
AS SELECT name,salary,address
FROM employees
WHERE se.state='CA';

這張表只含有employee表中來自加利福尼亞州的僱員的name、salary和address3個欄位的資訊。新表的模式是根據SELECT語句來生成的。使用這個功能的常見情況是從一個大的寬表中選取部分需要的資料集。這個功能不能用於外部表。可以回想下使用ALTER TABLE語句可以為外部表“引用”到一個分割槽,這裡本身沒有進行資料“裝載”,而是將元資料中指定一個指向資料的路徑。

4、匯出資料

我們如何從表中匯出資料呢?如果資料檔案恰好是使用者需要的格式,那麼只需要簡單地拷貝資料夾或者檔案就可以了:

hadoop fs -cp source_path target_path

否則,使用者可以使用INSERT…DIRECTORY…,如下面例子所示:

INSERT OVERWRITE LOCAL DIRECTORY '/tmp/caemployees'
SELECT name,salary,address
FROM employees
WHERE se.state='CA';

關鍵字OVERWRITE和LOCAL和前面的說明是一致的,路徑格式也和通常的規則一致。一個或者多個檔案將會被寫入到/tmp/ca_employees,具體個數取決於呼叫的reducer個數。
這裡指定的路徑也可以寫成全URL路徑(例如,hdfs://master-server/tmp/ca_employees)。
不管在源表中資料實際是怎麼儲存的,Htve會將所有的欄位序列化成字串寫人到檔案中。Hive使用和Hive內部儲存的表相同的編碼方式來生成輸出檔案。
作為提醒,我們可以在hive CLI中檢視結果檔案內容:

hive>!ls /tmp/ca_employees;
000000_0
hive>!cat /tmp/payroll/000000_0
John Doe 100000.0201San Antonio Circle Mountai ViewCA94040

是的,檔名是000000_0。如果有兩個或者多個reducer來寫輸出的話,那麼我們還可以看到其他相似命名的檔案(例如,000001_1)。
如上輸出內容看上去欄位間沒有分隔符,這是因為這裡並沒有把^A和^B顯示出來。
和向表中插人資料一樣,使用者也是可以通過如下方式指定多個輸出資料夾目錄的:

FROM staged_employees se
INSERT OVERWRITE DIRECTORY '/tmp/or_employees'
    SELECT * WHERE se.cty='US' and se.st='OR'
INSERT OVERWRITE DIRECTORY '/tmp/ca_employees'
    SELECT * WHERE se.cty='US' and se.st='CA'
INSERT OVERWRITE DIRECTORY '/tmp/il_employees'
    SELECT * WHERE se.cty='US' and se.st='IL';

對於定製輸出的資料是有一些限制的(當然,除非自己寫一個定製的OUTPUTFORMAT)。為了格式化欄位,Hive提供了一些內建函式,其中包括那些用於字串格式化的操作、例如轉換操作,拼接輸出操作等。
表的欄位分隔符可能是需要考量的。例如,如果其使用的是預設的^A分隔符,而使用者又經常匯出資料的話,那麼可能使用逗號或者製表鍵作為分隔符會更合適。
另一種變通的方式是定義一個“臨時”表,這個表的儲存方式配置成期望的輸出格式(例如,使用製表鍵作為欄位分隔符)。然後再從這個臨時表中查詢資料,並使用INSERT OVERWRITE DIRECTORY將查詢結果寫人到這個表中。和很多關係型資料庫不同的是,Hive中沒有臨時表的概念。使用者需要手動刪除任何建立了的但又不想長期保留的表。