Hive中collect_list全局保持順序
我用部署的是standalone模式,local單節點計算的時候,結果沒問題,當集羣計算的時候因爲是分佈式的,因此結果是亂序的。解決方法如下:
有以下Hive表的定義:
create table topic_recommend_score (
category_id int,
topic_id bigint,
score double,
rank int
);
這張表是我們業務裏話題推薦分值表的簡化版本。category_id代表分類ID,topic_id是話題ID,score是評分值。rank代表每個分類下話題分值的排名,用開窗函數計算出來的:row_number() over(partition by t.category_id order by t.score desc)
在對外提供推薦結果時,我們會將每個小組下排名前1000的話題ID取出,拼成一個逗號分隔的字符串,處理之後送入HBase供調用方查詢。拼合的SQL語句如下:
select category_id,
concat_ws(',',collect_list(cast(topic_id as string)))
from topic_recommend_score
where rank >= 1 and rank <= 1000
group by category_id;
看起來沒什麼問題?但實際上是錯誤的。輸出結果中總會有一些category_id對應的列表順序異常,比如本來排名正數與排名倒數的兩批ID調換了位置,即rank變成了n-3, n-2, n-1, n, 5, 6, 7, ..., n-4, 1, 2, 3, 4
。
產生這個問題的根本原因自然在MapReduce,如果啓動了多於一個mapper/reducer來處理數據,select出來的數據順序就幾乎肯定與原始順序不同了。考慮把mapper數固定成1比較麻煩(見我之前寫的那篇Hive調優文章),也不現實,所以要迂迴地解決問題:把rank加進來再進行一次排序,拼接完之後把rank去掉。如下:
select category_id,
regexp_replace(
concat_ws(',',
sort_array(
collect_list(
concat_ws(':',lpad(cast(rank as string),5,'0'),cast(topic_id as string))
)
)
),
'\\d+\:','')
from topic_recommend_score
where rank >= 1 and rank <= 1000
group by category_id;
這裏將rank放在了topic_id之前,用冒號分隔,然後用sort_array函數對collect_list之後的結果進行排序(只支持升序)。特別注意,rank必須要在高位補足夠的0對齊,因爲排序的是字符串而不是數字,如果不補0的話,按字典序排序就會變成1, 10, 11, 12, 13, 2, 3, 4...
,又不對了。
將排序的結果拼起來之後,用regexp_replace函數替換掉冒號及其前面的數字,大功告成。