本篇對Hive QL中join、left outer join、left semi join和full outer join等表連結操作作一簡要總結。
測試表準備
首先準備三張測試表,內容分別爲:
hql_jointest_a |
---|
idname 1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h |
create table if not exists hql_jointest_a (
id bigint,
name string
)
row format delimited
fields terminated by '\t'
lines terminated by '10'
stored as textfile;
hql_jointest_b |
---|
idscore 1 15 2 50 4 80 6 90 9 100 10 101 11 12 13 11 |
create table if not exists hql_jointest_b (
id bigint,
score bigint
)
row format delimited
fields terminated by '\t'
lines terminated by '10'
stored as textfile;
hql_jointest_c |
---|
idbook 1 數據結構 2 離散數學 3 經濟學 2 C語言基礎 3 離散數學 6 宏觀經濟學 2 邏輯學 |
create table if not exists hql_jointest_c (
id bigint,
book string
)
row format delimited
fields terminated by '\t'
lines terminated by '10'
stored as textfile;
爲使後面的論述不至於太生硬,我們給上述三張測試表的字段賦予一定的業務含義:id代表學生的學號,name是學生姓名,score是學生成績,book是學生擁有的書;在三張表中,id都是主鍵。第一張表表示某個班級(假設該班級爲CS-1)的學生名單,第二張表表示參加了某種考試(假設該考試爲T)的學生的成績,第三張表表示部分學生擁有的書。
join的用法
我們的第一個需求是,找出CS-1班中參加了T考試的學生姓名及其成績。該需求希望做到的是,取表a和表b中均存在的記錄,並使用主鍵id連結起來。代碼如下:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, b.id as id_b, b.score
from hql_jointest_a a
join hql_jointest_b b
on a.id = b.id;
輸出結果爲:
如果關聯的結果有重複記錄,那麼記錄會全部保留。爲了說明這一點,請先看下面一段代碼的輸出結果:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, c.id as id_c, c.book
from hql_jointest_a a
join hql_jointest_c c
on a.id = c.id;
這段代碼的業務場景可以描述爲:找出CS-1班中擁有書的同學姓名及其擁有的書名。輸出結果爲:
雖然id爲2的記錄有多條,但這些記錄彼此並不重複,表現爲最後一個字段book並不相同。
如果我們並不需要書名,只需要找出CS-1班中擁有書的同學姓名,那麼代碼如下:
set mapred.reduce.tasks = 1;
select a.id, a.name
from hql_jointest_a a
join hql_jointest_c c
on a.id = c.id;
這時輸出結果爲:
我們注意到,儘管書名book我們並沒有提取,但結果集還是保留了id爲2的所有三條記錄(當然,還有id爲3的兩條記錄)。在我們看來這些記錄是重複的、只需要保留1條就足夠的,這時可以使用distinct對關聯結果進行去重。
set mapred.reduce.tasks = 1;
select distinct a.id, a.name
from hql_jointest_a a
join hql_jointest_c c
on a.id = c.id;
輸出結果爲:
left outer join的用法
現在,需求方告訴我們,對於第一個需求,他們其實還想知道CS-1班中有哪些同學沒參加T考試(業務方的真實需求通常很坑爹難以捉摸,理解他們的真實意圖十分重要)。這時我們需要把CS-1班的所有學生記錄都取出來,同時,如果某學生參加T考試,則列出其分數。這裏需要以a爲左表,所取結果中應包含a的所有記錄。代碼如下:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, b.id as id_b, b.score
from hql_jointest_a a
left outer join hql_jointest_b b
on a.id = b.id;
輸出結果爲:
在結果集中,如果某學生沒有參加T考試,即其在b表中無相應記錄,那麼結果集的相應字段會被賦予NULL值。由此我們引出第一個需求中join的另外一種實現方式:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, b.id as id_b, b.score
from hql_jointest_a a
left outer join hql_jointest_b b
on a.id = b.id
where b.id is not null;
上述代碼中加上where b.id is not null,把在b表中值爲空的記錄剔除,實現的其實是傳統sql中的exists in操作。輸出結果爲:
現在,需求方又來麻煩你,把CS-1班中未參加T考試的學生取出來(——怎麼,你想做什麼?想處罰他們嗎?——不不,別誤會,只是想通知他們,這個考試必須參加。——哦,這樣。題外話:不能只是機械地被動地響應業務方需求,應該問清楚數據的應用場景,瞭解其想法後必要時予以指導。)。這個需求是,取a中存在但b中不存在的記錄。有了上面的闡述,我們很容易搞定代碼:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, b.id as id_b, b.score
from hql_jointest_a a
left outer join hql_jointest_b b
on a.id = b.id
where b.id is null;
以上代碼實現的其實就是傳統sql中的exists not in操作。輸出結果爲:
同樣的,在left outer join操作中,如果結果集出現重複記錄,可以使用distinct去重。
left semi join的用法
上一節我們其實已經變通地實現了exists in和exists not in操作。這裏我們使用Hive QL提供的另一種解決方案——left semi join來實現傳統sql的exists in操作。使用left semi join,有一個限制條件,即右表的字段只能出現在on子句中,而不能在select和where子句中引用。
回到第一個需求,找出CS-1班中參加T考試的學生。即取a中這樣的記錄,其id存在於b表中。
set mapred.reduce.tasks = 1;
select a.id, a.name
from hql_jointest_a a
left semi join hql_jointest_b b
on a.id = b.id;
輸出結果爲:
以下兩段代碼都是錯誤的:
set mapred.reduce.tasks = 1;
select a.id, a.name, b.id, b.score --b表出現在了select中
from hql_jointest_a a
left semi join hql_jointest_b b
on a.id = b.id;
set mapred.reduce.tasks = 1;
select a.id, a.name
from hql_jointest_a a
left semi join hql_jointest_b b
on a.id = b.id
where b.id >= 3; --b表出現在了where子句中
full outer join的用法
full outer join可以實現全連結。現在,我們需要取出CS-1班的全體學生,或者參加T考試的學生及其成績。即取a中存在或b中存在的記錄。代碼如下:
set mapred.reduce.tasks = 1;
select a.id as id_a, a.name, b.id as id_b, b.score
from hql_jointest_a a
full outer join hql_jointest_b b
on a.id = b.id;
輸出結果爲:
補充說明
left outer join where is not null與left semi join的聯繫與區別:兩者均可實現exists in操作,不同的是,前者允許右表的字段在select或where子句中引用,而後者不允許。
除了left outer join,Hive QL中還有right outer join,其功能與前者相當,只不過左表和右表的角色剛好相反。
另外,Hive QL中沒有left join、right join、full join以及right semi join等操作。