Hive.分組排序和TOP
轉載:https://blog.csdn.net/mashroomxl/article/details/23864685
HQL作為類SQL的查詢分析語言,到目前為止,應該也還未能達到其它流行的SQL(如Transact-SQL, MySQL)實現那樣完善。而在公司的生產環境中,我想應該也不會緊貼Hive版本更新的步伐,始終部署最新版的Hive;可能會滯後一兩個大版本神馬的;畢竟,雖然開源工具的透明性是一大利好,但與閉源的商業工具相比,在可用性等問題上的保障性還是略弱。
使用HQL進行離線分析使用者資料時,就算已經過聚合處理,但我們也可能只對那些突出的量化指標或者這些指標的增量變化感興趣,所以對聚合資料排序(按某列降序?增序?)成為很基本的需要,這在HQL這樣尚未成熟的語言中,結合orderby, limit子句可以毫無鴨梨地完成。
然而,即使我們可以把多個欄位放入order by子句中,並指定各個欄位的升降順序,如:
order by fieldA desc, fieldB [asc], fieldC desc
但排序操作始終是全域性的,我們有時候想要的卻是分組排序,即按fieldA排序以後,然後針對fieldA的每個值所對應的fieldB和(或)fieldC排序,而不是像order by那樣,針對所有fieldA的值對fieldB和(或)fieldC排序。
為了滿足這個需要,Transact-SQL提供了over, partition by句和 row_number()函式,而Hive也在0.11中引入over, partition by子句和rank函式,以此提供方便的視窗分析(分組分析)功能。
那對於0.11版之前的Hive,我們可以實現分組排序嗎?答案是肯定的,只是看起來沒那麼直接。
要實現這個需求,就需要請出distribute by, sort by這兩個重要角色了,distribute by能夠執行我們需要的分組功能,再結合Hive查詢的MapReduce Job特性,sort by又可以在分組內進行區域性排序。
當然,如果只有它們,我們只能得到排序後的一堆資料,但是無法知道每一條資料的名次,這就要自己編寫UDF函式,來確定和返回名次了,這個函式貌似在網路上流傳甚廣:
- public final class Rank extends UDF {
- private int counter;
- private String last_key ="";
- public int evaluate(final String key) {
- if (key == null) {
- this.last_key= "";
- this.counter= 0;
- return counter;
- }
- if(!key.equalsIgnoreCase(this.last_key)) {
- this.counter= 0;
- this.last_key= key;
- }
- return this.counter++;
- }
- }
在這裡我們忽略了自定義UDF的註冊的環節。。。在分組之後,應用Rank函式,這個函式始終跟蹤最新的引數值,在引數值連續相同的情況下,就將欄位counter作自增操作並返回這個計數值;而如果出現和上一次函式呼叫不同的引數值,Rank函式會重置其計數值欄位和key欄位(對應引數值)使我們得到一個int型別的名次值。
Hive裡稱這個為自定義函式,實際上每個自定義函式是一個繼承了UDF類,並提供和實現了evaluate方法的類;這個叫法略不福啊。
有了distribute by, sort by和這個Rank函式,我們就能夠實現分組排序了,編寫HQL查詢指令碼之前,我們還需要明確:
1. 分組欄位:distribute by的欄位是哪個(些)?
2. 排序依據:sort by的欄位是哪個(些)?
3. 函式引數:Rank函式需要String引數,我們應該給Rank函式傳遞什麼東西作為實參?
不曉得是因為以上這3個問題確實像我們在教科書上偶爾會看到的“容易證得”,還是因為博主們只是想了這個問題,並沒有實踐,反正我看到的網路上講分組排序(TOP)的博文都沒有明確地提出這3個問題。而按我的實踐經歷來看,初次實現這種需求的童鞋,就算看了這些博文,在得到正確結果之前,應該都會經歷各種困惑,下面我們從實際的場景來看看。
比如,我們需要查詢得到這樣的資料:每日使用應用myAPP的UV量TOP 30的裝置,和這TOP 10的裝置中每個裝置的流量(VV)最高的10項版塊內容(以內容ID來區分);
假定查詢所需的Hive表為:hiveTab_useraction
思路梗概:先用一個子查詢查出來每臺裝置的訪問內容,同時,用一個子查詢查出來TOP30的裝置,然後兩個表做內連線(join),然後在外層查詢中提取所需欄位列和資料列。
在這個流程裡面:
1)找出TOP10的裝置這個環節看起來沒有涉及分組排序,但還是需要考慮上面3個問題,因為我們要得到名次,而order by貌似不能同Rank函式友好協作(也有可能是我使用的方式不科學呢),而且,在以下呈現的指令碼中,我們還生成了一個常量字串的distribute_key;
2)然後在外層迴圈中更需要考慮上面3個問題。
請見HQL指令碼:
- select
- device_rank,
- device_info,
- vv_rank,
- pageID,
- act_vv
- from
- (
- select
- device_rank,
- device_info,
- (Rank(device_rank) + 1) as vv_rank,
- pageID,
- act_vv
- from
- (
- select
- t2.device_rank,
- t2.device_info,
- t1.pageID,
- t1.act_vv
- from
- (
- select
- fieldA as device_info,
- pageID,
- count(1) as act_vv
- from hiveTab_useraction
- where `date` >= dateStart and `date` <= dateEnd
- group by fieldA, pageID
- ) t1
- join
- (
- select
- (Rank(distribute_key) + 1) as device_rank,
- device_info,
- act_uv
- from
- (
- select
- distribute_key,
- device_info,
- act_uv
- from
- (
- select
- 'topdevice' as distribute_key,
- device_info,
- act_uv
- from
- (
- select
- fieldA as device_info,
- count(distinct uid) as act_uv
- from hiveTab_useraction
- where `date` >= dateStart and `date` <= dateEnd
- group by fieldA
- ) t
- order by act_uv desc
- limit 10
- ) t
- distribute by distribute_key
- sort by act_uv desc
- ) t
- ) t2 on (t1.device_info = t2.device_info)
- distribute by t2.device_rank
- sort by t2.device_rank, t1.act_vv desc
- ) t
- ) t
- where vv_rank <= 10
從指令碼實測來看,上面提到的需要明確的3個問題,真的很重要。另外,Rank函式返回的名次是從0開始,所以我們需要作+1處理。
Hive向普通使用者也開放了自行編寫、註冊和使用自定義函式的功能,這一點確實帶來了很大的擴充套件性。