【Hive】06-HiveQL:查詢
1、SELECT FROM語句
1.1、使用正則表示式來指定列
我們甚至可以使用正則表示式來選擇我們想要的列。下面的查詢將會從表stocks中選擇symbol列和所有列名以price作為字首的列:
SELECT symbol ,`price.*` FROM stocks;
1.2、使用列值進行計算
使用者不但可以選擇表中的列,還可以使用函式呼叫和算術表示式來操作列值。例如,我們可以查詢得到轉換為大寫的僱員姓名、僱員對應的薪水、需要繳納的聯邦稅收比例以及扣除稅收後再進行取整所得的稅後薪資。我們甚至可以通過呼叫內建函式map_values提取出deductions欄位map型別值的所有元素,然後使用內建的sum函式對map中所有元素進行求和運算。
SELECT upper(name),salary.deductions["FederaI Taxes"),round(salary*(1-deductions["Federal Taxes"])) FROM employees;
1.3、算術運算子
Hive中支援所有典型的算術運算子。
算術運算子接受任意的數值型別。不過,如果資料型別不同,那麼兩種型別中值範圍較小的那個資料型別將轉換為其他範圍更廣的資料型別。(範圍更廣在某種意義上就是指一個型別具有更多的位元組從而可以容納更大範圍的值。)例如,對於INT和BIGINT運算,INT會將型別轉換提升為BIGINT。對於INT和FLOAT運算,INT將提升為FLOAT。可以注意到我們的查詢語句中包含有(1-deductions[...])這個運算。因為欄位deductions是FLOAT型別的,因此數字1會提升為FLOAT型別。 當進行算術運算時,使用者需要注意資料溢位或資料下溢問題。Hive遵循的是底層Java中資料型別的規則,因此當溢位或下溢發生時計算結果不會自動轉換為更廣泛的資料型別。乘法和除法最有可能會引發這個問題。 使用者需要注意所使用的數值資料的數值範圍,並確認實際資料是否接近表模式中定義的資料型別所規定的數值範圍上限或者下限,還需要確認人們可能對這些資料進行什麼型別的計算。 如果使用者比較擔心溢位和下溢,那麼可以考慮在表模式中定義使用範圍更廣的資料型別。不過這樣做的缺點是每個資料值會佔用更多額外的記憶體。 使用者也可以使用特定的表示式將值轉換為範圍更廣的資料型別。 有時使用函式將資料值按比例從一個範圍縮放到另一個範圍也是很有用的,例如按照10次方冪進行除法運算或取log值(指數值),等等。這種資料縮放也適用於某些機器學習計算中,用以提高演算法的準確性和數值穩定性。
1.4、使用函式
我們前面的那個示例中還使用到了一個內建數學函式round(),這個函式會返回一個DOUBLE型別的最近整數。1.4.1、數學函式
需要注意的是函式floor、round和ceil〈“向上取整")輸人的是DOUBLE型別的 值,而返回值是BIGINT型別的,也就是將浮點型數轉換成整型了。在進行資料類 型轉換時,這些函式是首選的處理方式,而不是使用前面我們提到過的cast型別轉 換操作符。 同樣地,也存在基於不同的底(例如十六進位制)將整數轉換為字串的函式。1.4.2、聚合函式 聚合函式是一類比較特殊的函式,其可以對多行進行一些計算,然後得到一個結果值。 這類函式中最有名的兩個例子就是count和avg。函式用於計算有多少行資料(或者某列有多少值),而函式avg可以返回指定列的平均值。
通常,可以通過設定屬性hive.map.aggr值為true來提高聚合的效能,如下所示:
hive>SET hive.map.aggr=true;
hive>SELECT count(*),avg(salary) FROM employees;
正如這個例子所展示的,這個設定會觸發在map階段進行的“頂級”聚合過程。(非頂級的聚合過程將會在執行一個GROBY後進行。)不過,這個設定將需要更多的記憶體。 多個函式都可以接受DISTINCT表示式。例如,我們可以通過這種方式計算排重後的交易碼個數:
hive>SELECT count(DISTINCT symbol) FROM stocks;
0
警告:等一下,結果為0?當使用count(DISTINCT col)而同時col是分割槽列時存在這個bug。
1.4.3、表生成函式
與聚合函式“相反的”一類函式就是所謂的表生成函式,其可以將單列擴充套件成多列或者多行。這裡我們將簡要地討論下,然後列舉出Hive目前所提供的一些內建表生成函式。 下面我們通過一個例子來進行講解。如下的這個查詢語句將employees表中每行記錄中的subordinates欄位內容轉換成0個或者多個新的記錄行。如果某行僱員記錄subordinates欄位內容為空的話,那麼將不會產生新的記錄;如果不為空的話,那麼這個陣列的每個元素都將產生一行新記錄:
hive>SELECT explode(subordinates) AS sub FROM employees;
Mary Smith
Todd Jones
Bill King
上面的查詢語句中,我們使用AS sub子句定義了列別名sub。當使用表生成函式時,Hive要求使用列別名。使用者可能需要了解其他許多的特性細節才能正確地使用這些函式。
下面是一個使用函式parse_url_tuple的例子,其中我們假設存在一張名為url_table的表,而且表中含有一個名為url的列,列中儲存有很多網址:
SELECT parse_url_tuple(url,'HOST','PATH','QUERY') as (host,path,query) FROM urlt_able;
1.4.4、其他內建函式
1.5、LIMIT語句
典型的查詢會返回多行資料。LIMIT子句用於限制返回的行數:
SELECT upper(name),salary,deductions["Federal Taxes"],round(salary*(1-deductions["Federa1Taxes"]))
FROM employees LIMIT2;
1.6、列別名
前面的示例查詢語句可以認為是返回一個由新列組成的新的關係,其中有些新產生的結果列對於表來說是不存在的。通常有必要給這些新產生的列起一個名稱,也就是別名。下面這個例子對之前的那個查詢進行了修改,為第3個和第4個欄位起了別名,別名分別為fed_taxes和salary_minus_fed_taxes。
SELECT upper(name),salary,deductions["Federal Taxes"] as fed_taxes,
round(salary * (1-deductions["Federal Taxes")) as salary_minus_fed_taxes
FROM employees LIMIT 2;
1.7、巢狀SELECT語句
對於巢狀查詢語句來說,使用別名是非常有用的。下面,我們使用前面的示例作為一個巢狀查詢:
FROM (
SELECT upper(name),salary,deductions["Federal Taxes"] as fed_taxes,
round(salary * (1-deductions["Federal Taxes"])) as salary_minus_fed_taxes
FROM empioyees) e
SELECT e.name,e.salary_minus_fed_taxes
WHERE e.salary_minus_fed_taxes > 70000;
從這個巢狀查詢語句中可以看到,我們將前面的結果集起了個別名,稱之為e,在這個語句外面巢狀查詢了name和salary_minus_fed_taxes兩個欄位,同時約束後者的值要大於70000。
1.8、CASE…WHEN…THEN句式
CASE…WHEN…THEN語句和if條件語句類似,用於處理單個列的查詢結果。
SELECT name,salary,
CASE
WHEN salary<50000.0 THEN 'low'
WHEN salary>50000.0 AND salary<70000.0 THEN 'middle'
WHEN salary>=70000.0 AND salary<100000.0 THEN 'high'
ELSE 'veryhigh'
END AS bracket FROM employees;
1.9、什麼情況下Hive可以避免進行MapReduce
對於本書中的查詢,如果使用者進行過執行的話,那麼可能會注意到大多數情況下查詢都會觸發一個MapReduce任務(job)。Hive中對某些情況的查詢可以不必使用MapReduce,也就是所謂的本地模式,例如:
SELECT * FROM employees;
在這種情況下,Hive可以簡單地讀取employees對應的儲存目錄下的檔案,然後輸出格式化後的內容到控制檯。 對於WHERE語句中過濾條件只是分割槽欄位這種情況(無論是否使用LIMIT語句限制輸出記錄條數),也是無需MapReduce過程的。
SELECT * FROM employees WHERE country='US' AND state='CA' LIMIT 100;
此外,如果屬性hive.exec.mode.local.auto的值設定為true的話,Hive還會嘗試使用本地模式執行其他的操作:
set hive.exec.mode.local.autoi=true;
否則,Hive使用MapReduce來執行其他所有的查詢。 提示:最好將set hive.exec.mode.local.autoi=true;這個設定增加到你的$HOME/.hiverc配置檔案中。
2、WHERE語句
SELECT語句用於選取欄位,WHERE語句用於過濾條件,兩者結合使用可以查詢到符合過濾條件的記錄。和SELECT語句一樣,在介紹WHERE語句之前我們已經在很多的簡單例子中使用過它了。之前都是假定使用者是見過這樣的語句的,現在我們將更多地探討一些細節。 WHERE語句使用謂詞表達式,對於列應用在謂詞操作符上的情況,稍後我們將進行討論。有幾種謂詞表達式可以使用AND和OR相連線。當謂詞表達式計算結果為時,相應的行將被保留並輸出。 謂詞可以引用和SELECT語句中相同的各種對於列值的計算。這裡我們修改下之前的對於聯邦稅收的查詢,過濾保留那些工資減去聯邦稅後總額大於70000的查詢結果:
SELECT name,salary,deductions["Federal Taxes"],
salary(1-deductions["Federal Taxes"])
FROM employees
WHERE round(salary*(1-deductions["Federal Taxes"]))>70000;
這個查詢語句有點難看,因為第2行的那個複雜的表示式和WHERE後面的表示式是一樣的。下面的查詢語句通過使用一個列別名消除了這裡表示式重複的問題,但是不幸的是它不是有效的:
SELECT name,salary,deductions["Federa1Taxes"],
salary * (1-deductions["FederaITaxes"])as salary_minus_fed_taxes
FROM employees
WHERE round(salary_minus_fed_taxes)>70000;
FAILED:Error in semantic analysis:Line4:13 lnvalid table alias or column refernce 'salary_minus_fed_taxes':(possible column names are: name,salary,subordinates,deductions,address) 正如錯誤資訊所提示的,不能在WHERE語句中使用列別名。不過,我們可以使用一個巢狀的SELECT語句:
SELECT e.* FROM
(SELECT name,salary,deductions["Federal Taxes"] as ded,
salary* (1-deductions["Federal Taxes"]) as salary_minus_fed_taxes
FROM employees) e
WHERE round(e.salary_minus_fed_taxes)>70000;
2.1.關於浮點數比較
浮點數比較的一個常見陷阱出現在不同型別間作比較的時候(也就是FLOAT和DOUBLE比較)。思考下面這個對於員工表的查詢語句,該語句將返回員工姓名、工資和聯邦稅,過濾條件是薪水的減免稅款超過0.2(20%):
SELECT name,salary,deductions['Federal Taxes']
FROM employees WHERE deductions['Federal Taxes']>0.2;
等一下!為什麼deductions['FederalTaxes']=0.2的記錄也被輸出了? 這是個Hive的Bug嗎?確實有個issue是關於這個問題的,但是其實際上反映了內部是如何進行浮點數比較的,這個問題幾乎影響了在現在數字計算機中所有使用各種各樣程式語言編寫的軟體。 當用戶寫一個浮點數時,比如0.2,Hive會將該值儲存為DOUBLE型的。我們之前定義deductions這個map的值的型別是FLOAT型的,這意味著Hive將隱式地將稅收減免值轉換為DOUBLE型別後再進行比較。這樣應該是可以的,對嗎? 事實上,這樣行不通。這裡解釋下為什麼不能。數字0.2不能夠使用FLOAT或DOUBLE進行準確表示。在這個例子中,0.2的最近似的精確值應略大於0.2,也就是0.2後面的若干個0後存在非零的數值。
為了簡化一點,實際上我們可以說0.2對於FLOAT型別是0.2000001,而對於DOUBLE型別是0.200000000001。這是因為一個8個位元組的DOUBLE值具有更多的小數位(也就是小數點後的位數)。當表中的FLOAT值通過Hive轉換為DOUBLE值時,其產生的DOUBLE值是0.200000100000,這個值實際要比0.200000000001大。這就是為什麼這個查詢結果像是使用了>=而不是>了。 這個問題並非僅僅存在於Hive中或Java中(Hive是使用Java實現的)。而是所有使用IEEE標準進行浮點數編碼的系統中存在的一個普遍的問題。 然而,Hive中有兩種規避這個問題的方法。 首先,如果我們是從TEXTFILE文字檔案中讀取資料的話,也就是目前為止我們所假定使用的儲存格式,那麼Hive會從資料檔案中讀取字串“0.2”然後將其轉換為一個真實的數字。我們可以在表模式中定義對應的欄位型別為DOUBLE而不是FLOAT。這樣我們就可以對deductions["Federal Taxes']這個DOUBLE值和0.2這個DOUBLE值進行比較。不過,這種變化會增加我們查詢時所需的記憶體消耗。同時,如果儲存格式是二進位制檔案格式的話,我們也不能簡單地進行這樣的改變。 第2個規避方案是顯式地指出0.2為FLOAT型別的。Java中有一個很好的方式能夠達到這個目的:只需要在數值末尾加上字母F或f(例如,0.2f)。不幸的是,Hive並不支援這種語法,這裡我們必須使用cast操作符。 下面這個是修改後的查詢語句,其將0.2型別轉換為FLOAT型別了。通過這個修改,返回結果是符合預期的:
SELECT name,salary,deductions['FederaI Taxes'] FROM employees
WHERE deductions['Federal Taxes"]>cast (0.2 AS FLOAT);
注意cast操作符內部的語法:數值 AS FLOAT。 實際上,還有第3種解決方案,即:和錢相關的都避免使用浮點數。 對浮點數進行比較時,需要保特極端謹慎的態度。要避免任何從窄型別隱式轉換到更廣泛型別的操作。
3、GROUPBY語句
GROUPBY語句通常會和聚合函式一起使用,按照一個或者多個列對結果進行分組,然後對每個組執行聚合操作。
SELECT year(ymd),avg(price_close) FROM stocks
WHERE exchange='NASDÄQ' AND symbol='AAPL'
GROUP BY year(ymd);
HAVING語句
HAVING子句允許使用者通過一個簡單的語法完成原本需要通過子查詢才能對GROUP BY語句產生的分組進行條件過濾的任務。如下是對前面的查詢語句增加一個HAVING語句來限制輸出結果中年平均收盤價要大於$50.0:
SELECT year(ymd),avg(price_close) FROM stocks
WHERE exchange='NASDAQ' AND symbol='AAPL'
GROUP BY year(ymd)
HAVING avg(price_close)>50.0;
如果沒使用HAVING子句,那麼這個查詢將需要使用一個巢狀SELECT子查詢:
SELECT s2.year,s2.avg FROM
(SELECT year(ymd) AS year,avg(price_close) AS avg FROM stocks
WHERE exchange='NASDAQ' AND symbol='AAPL'
GROUP BY year(ymd)) s2
WHERE s2.avg>50.0;
4、JOIN語句
Hive支援通常的SQL JOIN語句,但是隻支援等值連線。
4.1、INNER JOIN
內連線(INNER JOIN)中,只有進行連線的兩個表中都存在與連線標準相匹配的資料才會被保留下來。例如,如下這個查詢對蘋果公司的股價(股票程式碼AAPL)和IBM公司的股價(股票程式碼IBM)進行比較。股票表stocks進行自連線,連線條件是ymd欄位(也就是year-month-day)內容必須相等。我們也稱ymd欄位是這個查詢語句中的連線關鍵字。
SELECT a.ymd,a.price_close,b.price_close
FROM stocks a JOIN stocks b ON a.ymd=b.ymd
WHERE a.symbol='AAPL' AND b.symbol='IBM';
ON子句指定了兩個表間資料進行連線的條件。WHERE子句限制了左邊表是AAPL的記錄,右邊表是IBM的記錄。同時使用者可以看到這個查詢中需要為兩個表分別指定表別名。 眾所周知,IBM要比Apple老得多。IBM也比Apple具有更久的股票交易記錄。不過,既然這是一個內連線(INNERJOIN),IBM的1984年9月7日前的記錄就會被過濾掉,也就是Apple股票交易日的第一天算起! 標準SQL是支援對連線關鍵詞進行非等值連線的,例如下面這個顯示Apple和IBM對比資料的例子,連線條件是Apple的股票交易日期要比IBM的股票交易日期早。這個將會返回很少資料!標準SQL是支援對連線關鍵詞進行非等值連線的,Hive中不支援,主要原因是通過MapReduce很難實現這種型別的連線。 同時,Hive目前還不支援在ON子句中的謂詞間使用OR。 通過下面的例子我們來看下非自連線操作。dividends表的資料同樣來自於infochimps.org:
CREATE EXTERNAL TABLE IF NOT EXISTS dividends(
ymd STRING,
dividend FLOAT
)
PARTITIONED BY(exchange STRING,symbol STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
下面這個例子就是蘋果公司的stocks表和dividends表按照欄位ymd和欄位symbol作為等值連線鍵的內連線(INNER JOIN):
SELECT s.ymd,s.symbol,s.price_close,d.dividend
FROM stocks s JOIN dividends d ON s.ymd=d.ymd AND s.symbol=d.symbol
WHERE s.symbol='AAPL';
使用者可以對多於2張表的多張表進行連線操作。下面我們來對Apple公司、IBM公司和GE公司並排進行比較:
SELECT a.ymd,a.price_close,b.price_close,c.price_close
FROM stocks a JOIN stocks b ON a.ymd=b.ymd
JOIN stocks c ON a.ymd=c.ymd
WHERE a.symbol='AAPL' AND b.symbol='IBM' AND c.symbol='GE';
大多數情況下,Hive會對每對JOIN連線物件啟動一個MapReduce任務。本例中,會首先啟動一個MapReducejob對錶a和表b進行連線操作,然後會再啟動一個MapReducejob將第一個MapReducejob的輸出和表c進行連線操作。 提示:為什麼不是表b和表c先進行連線操作呢?這是因為Hive總是按照從左到右的順序執行的。
4.2、JOIN優化
在前面的那個例子中,每個ON子句中都使用到了a.ymd作為其中一個JOIN連線鍵。在這種情況下,Hive通過一個優化可以在同一個MapReduce job中連線3張表。同樣,如果b.ymd也用於ON子句中的話,那麼也會應用到這個優化。 當對3個或者更多個表進行JOIN連線時,如果每個ON子句都使用相同的連線鍵的話,那麼只會產生一個MapReduce job。 Hive同時假定查詢中最後一個表是最大的那個表。在對每行記錄進行連線操作時,它會嘗試將其他表快取起來,然後掃描最後那個表進行計算。因此,使用者需要保證連續查詢中的表的大小從左到右是依次增加的。
幸運的是,使用者並非總是要將最大的表放置在查詢語句的最後面的。這是因為Hive還提供了一個“標記"機制來顯式地告之查詢優化器哪張表是大表,使用方式如下:
SELECT /*+STREAMTABLE(s)*/ s.ymd,s.symbol,s.price_close,d.dividend
FROM stocks s JOIN dividends d ON s.ymd=d.ymd AND s.symbol=d.symbol
WHERE s.symbol='AAPL';
現在Hive將會嘗試將表stocks作為驅動表,即使其在查詢中不是位於最後面的。
4.3、LEFT OUTER JOIN
左外連線通過關鍵字LEFTOUTER進行標識:
SELECT s.ymd,s.symbol,s.price_close,d.dividend FROM stocks s LEFT OUTER JOIN dividends d ON s.ymd=d.ymd AND s.symboI=d.symbol WHERE s.symbol='AAPL'
在這種JOIN連線操作中,JOIN操作符左邊表中符合WHERE子句的所有記錄將會被返回。JOIN操作符右邊表中如果沒有符合ON後面連線條件的記錄時,那麼從右邊表指定選擇的列的值將會是NULL。 因此,在這個結果集中,我們看到Apple公司的股票記錄都返回了,而d.divldend欄位的值通常是NULL。
4.4、OUTER JOIN
在我們討論其他外連線之前,讓我們來討論一個使用者應該明白的問題。 回想下,前面我們說過,通過在WHERE子句中增加分割槽過濾器可以加快查詢速度。為了提高前面那個查詢的執行速度,我們可以對兩張表的exchange欄位增加謂詞限定:
SELECT s.ymd,s.symbol,s.price_close,d.dividend FROM stocks s LEFT OUTER JOIN dividends d ON
s.ymd=d.ymd AND s.symbol=d.symbol WHERE s.symbol='AAPL' AND s.exchange='NASDAQ' AND d.exchange='NASDAQ'
不過,這時我們發現輸出結果改變了,雖然我們可能認為我們不過增加了一個優化! 我們重新獲得每年4條左右的股票交易記錄,而且我們發現每年對應的股息值都是非NULL的。換句話說,這個效果和之前的內連線(INNER JOIN)是一樣的! 在大多數的SQL實現中,這種現象實際上是比較常見的。之所以發生這種情況,是因為會先執行JOIN語句,然後再將結果通過WHERE語句進行過濾。在到達WHERE語句時,d.exchange欄位中大多數值為NULL,因此這個“優化”實際上過濾掉了那些非股息支付日的所有記錄。 一個直接有效的解決方式是:移除掉WHERE語句中對dividends表的過濾條件,也就是去除掉d.exchange='NASDAQ'這個限制條件。
SELECT s.ymd,s.symbol,s.price_close,d.dåvidend FROM stocks s LEFT OUTER JOIN dividends d ON
s.ymd=d.ymd AND s.symbol=d.symbol WHERE s.symbol='AAPL 'AND s.exchange='NASDAQ'
這種方式並非很令人滿意。使用者可能會想知道是否可以將WHERE語句中的內容放置到ON語句裡,至少知道分割槽過濾條件是否可以放置在ON語句中。對於外連線OUTER JOIN)來說是不可以這樣的。
SELECT s.ymd,s.symbol,s.price_close.d.dividend
FROM stocks s LEFT OUTER JOIN dividends d
ON s.ymd=d.ymd AND s.symbol=d.symbol
AND s.symbol="APPL" AND s.exchange='NASDAQ' AND exchange='NASDAQ'
對於外連線(OUTER JOIN)會忽略掉分割槽過濾條件。不過,對於內連線(INNER JOIN)使用這樣的過濾謂詞確實是起作用的! 幸運的是,有一個適用於所有種類連線的解決方案,那就是使用巢狀SELECT語句:
SELECT s.ymd,s.symbol,s.price_close,d.dividend FROM
(SELECT * FROM stocks WHERE symbol='AAPL' AND exchange='NASDAQ') s
LEFT OUTER JOIN
(SELECT * FROM dividends WHERE symbol='AAPL' ADD exchange='NASDAQ')d
ON s.ymd=d.ymd;
巢狀SELECT語句會按照要求執行“下推”過程,在資料進行連線操作之前會先進行分割槽過濾。提示:WHERE語句在連線操作執行後才會執行,因此WHERE語句應該只用於過濾那些非NULL值的列值。同時,和Hive的文件說明中相反的是,ON語句中的分割槽過濾條件外連線(OUTER JOIN)中是無效的,不過在內連線(INNER JOIN)中是有效的。
4.5、RIGHT OUTER JOIN
右外連線(RIGHTOUTERJOIN)會返回右邊表所有符合WHERE語句的記錄。左表中匹配不上的欄位值用NULL代替。 這裡我們調整下stocks表和divideneds表的位置來執行右外連線,並保留SELECT語句不變:
SELECT s.ymd,s.symbol,s.price_close,d.dividend
FROM dividends d RIGHT OUTER JOIN stocks s ON d.ymd=s.ymd AND d.symbol=s.symbol
WHERE s.symbol='AAPL'
4.6、FULL OUTER JOIN
最後介紹的完全外連線(FULL OUTER JOIN)將會返回所有表中符合WHERE語句條件的所有記錄。如果任一表的指定欄位沒有符合條件的值的話,那麼就使用NULL值替代。 如果我們將前面的查詢改寫成一個完全外連線查詢的話,事實上獲得的結果和之前的一樣。這是因為不可能存在有股息支付記錄而沒有對應的股票交易記錄的情況。
SELECT s.ymd,s.symböl,s.price_close,d.dividend
FROM dividends d FULL OUTER JOIN stocks s ON d.ymd=s.ymd AND d.symbol=s.symbol
WHERE s.symbol='AAPL'
4.7、LEFT SEMI-JOIN
左半開連線(LEFT SEMI-JOIN)會返回左邊表的記錄,前提是其記錄對於右邊表滿足ON語句中的判定條件。對於常見的內連線(INNERJOIN)來說,這是一個特殊的、優化了的情況。大多數的SQL方言會通過IN…EXISTS結構來處理這種情況。例如下面的例中所示的查詢,其將試圖返回限定的股息支付日內的股票交易記錄,不過這個查詢Hive是不支援的。 例、Hive中不支援的查詢
SELECT s.ymd,s.symbol,s.price_close FROM stocks s
WHERE s.ymd,s.symbol IN
(SELECT d.ymd,d.symbol FROM dividends d)
不過,使用者可以使用如下的LEFT SEMIJOIN語法達到同樣的目的:
SELECT s.ymd,s.symbol,s.price_close
FROM stocks s LEFT SEMI JOIN dividends d ON s.ymd=d.ymd AND s.symbol
請注意,SELECT和WHERE語句中不能引用到右邊表中的欄位。
Hive不支援右半開連線(RIGHTSEMI-JOIN). SEMI-JOIN比通常的INNER JOIN要更高效,原因如下:對於左表中一條指定的記錄,在右邊表中一旦找到匹配的記錄,Hive就會立即停止掃描。從這點來看,左邊表中選擇的列是可以預測的。
4.8、笛卡爾積JOIN
笛卡爾積是一種連線,表示左邊表的行數乘以右邊表的行數等於笛卡爾結果集的大小、也就是說如果左邊表有5行資料,而右邊表有6行資料,那麼產生的結果將是30行資料: SELECT * FROM stocks JOIN dividends; 如上面的查詢,以stocks表和dividends表為例,實際上很難找到合適的理由來執行這類連線,因為一隻股票的股息通常並非和另一隻股票配對。此外,笛卡爾積會產生大量的資料。和其他連線型別不同,笛卡爾積不是並行執行的,而且使用MapReduce計算架構的話,任何方式都無法進行優化。 這裡非常有必要指出,如果使用了錯誤的連線(JOIN)語法可能會導致產生一個執行時間長、執行緩慢的笛卡爾積查詢。例如,如下這個查詢在很多資料庫中會被優化成內連線(INNER JOIN),但是在Hive中沒有此優化:
SELECT * FROM stocks JOIN dividends
WHERE stock.symbol=dividends.symbol and stock.symbol='AAPL'
在Hive中,這個查詢在應用WHERE語句中的謂詞條件前會先進行完全笛卡爾積計算。 這個過程將會消耗很長的時間。如果設定屬性hive.mapred.mode值為strict的話,Hive會阻止使用者執行笛卡爾積查詢。提示:笛卡爾積在一些情況下是很有用的·例如,假設有一個表表示使用者偏好,另有一個表表示新聞文章,同時有一個演算法會推測出使用者可能會喜歡讀哪些文章·這個時候就需要使用笛卡爾積生成所有使用者和所有網頁的對應關係的集合。
4.9、map-side JOIN
如果所有表中只有一張表是小表,那麼可以在最大的表通過mapper的時候將小表完全放到記憶體中。Hive可以在map端執行連線過程(稱為map-sideJOIN),這是因為Hive可以和記憶體中的小表進行逐一匹配,從而省略掉常規連線操作所需要的reduce過程。 即使對於很小的資料集,這個優化也明顯地要快於常規的連線操作。其不僅減少了reduce過程,而且有時還可以同時減少map過程的執行步驟。 stocks表和dividends表之間的連線操作也可以利用到這個優化,因為dividends表中的資料集很小,已經可以全部放在記憶體中快取起來了。 在Hivev0.7之前的版本中,如果想使用這個優化,需要在查詢語句中增加一個標記來進行觸發。如下面的這個內連線(INNER JOIN)的例子所示:
在一個比較快的MacBookPro膝上型電腦上執行上面這個優化後的查詢大約需要23s,這明顯比優化前執行所消耗的33s要快。在相同的股票樣本資料集上執行,執行速度提高了大約3。 從Hive v0.7版本開始,廢棄了這種標記的方式,不過如果增加了這個標記同樣是有效的。如果不加上這個標記,那麼這時使用者需要設定屬性hive.auto.convert.join的值為true,這樣Hive才會在必要的時候啟動這個優化。預設情況下這個屬性的值是false。
hive>set hive.auto.convert.join=true;
hive>SELECT s.ymd,s.symbol,s.price_close,d.dividend
>FROM stocks s JOIN dividends d ON s.ymd=d.ymd AND s.symbol=d.symbol
>WHERE s.symbol='AAPL';
需要注意的是,使用者也可以配置能夠使用這個優化的小表的大小。如下是這個屬性的預設值(單位是位元組): hive.mapjoin.smalltable.flesze=25000000 如果使用者期望Hive在必要的時候自動啟動這個優化的話,那麼可以將這一個(或兩個)屬性設定在$HOME/.hiverc檔案中。 Hive對於右外連線(RIGHT OUTER JOIN)和全外連線(FULL OUT ERJOIN)不支援這個優化。 如果所有表中的資料是分桶的,那麼對於大表,在特定的情況下同樣可以使用這個優化,詳細介紹請參見“分桶表資料儲存”中的介紹。簡單地說,表中的資料必須是按照ON語句中的鍵進行分桶的,而且其中一張表的分桶的個數必須是另一張表分桶個數的若干倍。當滿足這些條件時,那麼Hive可以在map階段按照分桶資料進行連線·因此這種情況下,不需要先獲取到表中所有的內容,之後才去和另一張表中每個分桶進行匹配連線。 不過,這個優化同樣預設是沒有開啟的。需要設定引數hive.optimize.bucketmapjoin為true才可以開啟此優化:
set hive.optimize.bucketmapjoin=true;
如果所涉及的分桶表都具有相同的分桶數,而且資料是按照連線鍵或桶的鍵進行排序的,那麼這時Hwe可以執行一個更快的分類.合併連線(sort-mergeJOIN)。同樣地,這個優化需要需要設定如下屬性才能開啟:
set hive.input.format.org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
set hive.optimize.bucketmapjoin=true;
set hive.optimize.bucketmapjoin.sortedmerge=true;
6.5、ORDERBY和SORTBY
Hive中ORDERBY語句和其他的SQL方言中的定義是一樣的。其會對查詢結果集執行一個全域性排序。這也就是說會有一個所有的資料都通過一個reducer進行處理的過程。對於大資料集,這個過程可能會消耗太過漫長的時間來執行。 Hive增加了一個可供選擇的方式,也就是SORTBY,其只會在每個reducer中對資料進行排序,也就是執行一個區域性排序過程。這可以保證每個reducer的輸出資料都是有序的(但並非全域性有序)。這樣可以提高後面進行的全域性排序的效率。 對於這兩種情況,語法區別僅僅是,一個關鍵字是ORDER,另一個關鍵字是SORT。 使用者可以指定任意期望進行排序的欄位,並可以在欄位後面加上ASC關鍵字(預設的),表示按升序排序,或加DESC關鍵字,表示按降序排序。 下面是一個使用ORDER BY的例子:
SELECT s.ymd,s.symbol,s.price_close
FROM stocks s
ORDER BY s.ymd ASC,s.symbol DESC
下面是一個類似的例子,不過使用的是SORT BY。
SELECT s.ymd,s.symbol,s.price_close
FROM stocks s
SORT BY s.ymd ASC,s.symbol DESC;
上面介紹的兩個查詢看上去幾乎一樣,不過如果使用的reducer的個數大於1的話,那麼輸出結果的排序就大不一樣了。既然只保證每個reducer的輸出是區域性有序的,那麼不同reducer的輸出就可能會有重疊的。
因為ORDER BY操作可能會導致執行時間過長,如果屬性hive.mapred.mode的值是strict的話,那麼Hive要求這樣的語句必須加有LIMIT語句進行限制。預設情況下,這個屬性的值是nonstrict,也就是不會有這樣的限制。
6、含有SORT BY的DISTRIBUTE BY
DISTRIBUTE BY控制map的輸出在reducer中是如何劃分的。MapReduce job中傳輸的所有資料都是按照鍵值對的方式進行組織的,因此Hive在將使用者的查詢語句轉換成MapReducejob時,其必須在內部使用這個功能。 通常,使用者不需要擔心這個特性。不過對於使用了Streaming特性以及一些狀態為UDAF(使用者自定義聚合函式)的查詢是個例外。還有,在另外一個場景下,使用這些語句是有用的。 預設情況下,MapReduce計算框架會依據map輸人的鍵計算相應的雜湊值,然後按照得到的雜湊值將鍵·值對均勻分發到多個reducer中去。不過不幸的是,這也就意味著當我們使用SORTBY時,不同reducer的輸出內容會有明顯的重疊,至少對於排列順序而言是這樣,即使每個reducer的輸出的資料都是有序的。 假設我們希望具有相同股票交易碼的資料在一起處理。那麼我們可以使用DISTRIBUTE BY來保證具有相同股票交易碼的記錄會分發到同一個reducer中進行處理,然後使用SORTBY來按照我們的期望對資料進行排序。如下這個例子就演示了這種用法:
SELECT s.ymd,s.symbol,s.price_close
FROM stocks s
DISTRIBUTE BY s.symbol
SORT BY s.symbol ASC,s.ymd ASC
當然,上面例子中的ASC關鍵字是可以省略掉的,因為其就是預設值。這裡加上了ASC關鍵字的原因,稍後我們將會進行簡要的闡述。 DISTRIBUTE BY和GROUP BY在其控制著reducer是如何接受一行行資料進行處理這方面是類似的,而SORTBY則控制著reducer內的資料是如何進行排序的。需要注意的是,Hive要求DISTRIBUTEBY語句要寫在DORTBY語句之前。
7、CLUSTER BY
在前面的例子中,s.symbol列被用在了DISTRIBUTE BY語句中,而s.symbol列和s.ymd位於SORT BY語句中。如果這2個語句中涉及到的列完全相同,而且採用的是升序排序方式(也就是預設的排序方式),那麼在這種情況下,CLUSTER BY就等價於前面 的2個語句,相當於是前面2個句子的一個簡寫方式。 如下面的例子所示,我們將前面的查詢語句中SORTBY後面的s.ymd欄位去掉而只對s.symbol欄位使用CLUSTER BY語句:
SELECT s.ymd,s.symbol,s.price_close
FROM stocks s
CLUSTER BY s.symbol;
因此排序限制中去除掉了s.ymd欄位,所以輸出中展示的是股票資料的原始排序方式,也就是降序排列。 使用DISTRIBUTEBY…SORTBY語句或其簡化版的CLUSTERBY語句會剝奪SORTBY的並行性,然而這樣可以實現輸出檔案的資料是全域性排序的。
8、型別轉換
在“基本資料型別”中我們簡要提及了Hive會在適當的時候,對數值型資料型別進行隱式型別轉換,其關鍵字是cast.例如,對不同型別的2個數值進行比較操作時就會有這種隱式型別轉換。 這裡我們討論下cast函式,使用者可以使用這個函式對指定的值進行顯式的型別轉換。 回想一下,前面我們介紹過的employees表中町列是使用FLOAT資料型別的。現在,我們假設這個欄位使用的資料型別是STRING的話,那麼我們如何才能將其作為FLOAT值進行計算呢? 如下這個例子會先將值轉換為FLOAT型別,然後才會執行數值大小比較過程:
SELECT name,salary FROM employees
WHERE cast(salary AS FLOAT)<100000.0;
型別轉換函式的語法是cast(value AS TYPE)。如果例子中的salary欄位的值不是合法的浮點數字符串的話,那麼結果會怎麼樣呢?這種情況下,Hive會返回NULL。 需要注意的是,將浮點數轉換成整數的推薦方式是使用round()或者flo0函式,而不是使用型別轉換操作符cast。型別轉換BINARY值 Hive v0.8.0版本中新引人的BARY型別只支援將BARY型別轉換為STRING型別。 不過,如果使用者知道其值是數值的話,那麼可以通過巢狀t()的方式對其進行型別轉換,如下面例子所示,其中b欄位型別是BINARY:
SELECT (2.0*cast(cast(b as string) as double)) from src
使用者同樣可以將STRING型別轉換為BINARY型別。
9、抽樣查詢
對於非常大的資料集,有時使用者需要使用的是一個具有代表性的查詢結果而不是全部結果。Hive可以通過對錶進行分桶抽樣來滿足這個需求。 在下面這個例子中,假設numbers表只有number欄位,其值是1到10。 我們可以使用rand()函式進行抽樣,這個函式會返回一個隨機值。前兩個查詢都返回了兩個不相等的值,而第3個查詢語句無返回結果:
如果我們是按照指定的列而非rand()函式進行分桶的話,那麼同一語句多次執行的返回值是相同的:
分桶語句中的分母表示的是資料將會被雜湊的桶的個數,而分子表示將會選擇的桶的個數:
9.1、資料塊抽樣
Hive提供了另外一種按照抽樣百分比進行抽樣的方式,這種是基於行數的,按照輸入路徑下的資料塊百分比進行的抽樣: SELECT * FROM numbersflat TABLESAMPLE(0.1 PERCENT) s; 這種抽樣方式不一定適用於所有的檔案格式·另外,這種抽樣的最小抽樣單元是一個HDFS資料塊·因此,如果表的資料大小小於普通的塊大小128MB的話,那麼將會返回所有行。 基於百分比的抽樣方式提供了一個變數,用於控制基於資料塊的調優的種子資訊:
<property>
<name>hive.sample.seednumber</name>
<value>0</value>
</property>
9.2、分桶表的輸入裁剪
從第一次看TABLESAMPLE語句,精明的使用者可能會得出“如下的查詢和TABLESAMPLE操作相同”的結論:
SELECT * FROM numbersflat WHERE number%2=0
對於大多數型別的表確實是這樣的。抽樣會掃描表中所有的資料,然後在每N行中抽取一行資料。不過,如果TABLESAMPLE語句中指定的列和CLUSTERED BY語句中指定的列相同,那麼TABLESAMPLE查詢就只會掃描涉及到的表的哈斯分割槽下的資料: 因為這個表已經聚整合3個數據桶了,下面的這個查詢可以高效地僅對其中一個數據桶進行抽樣: