HIVE學習五:自定義函數UDF、transform腳本和lateral view

自定義函數UDF

雖然hive已經提供了足夠多的內置函數供我們使用,但是有時候需要自己去寫函數來處理業務數據。
以官方給的UDF例子來說明,代碼如下
創建一個將字符串轉換成小寫的函數,Lower類需要繼承UDF類,並在Lower類定義訪問類型爲public方法名爲evaluate的函數

package com.utstar.patrick;

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

public class Lower extends UDF {
    public Text evaluate(final Text s) {
        if (s == null) { return null; }
        return new Text(s.toString().toLowerCase());
    }
}

將寫的代碼打成一個jar包,假如jar名爲udf.jar,並上傳到服務器目錄/usr/local/src/hive/jars
執行add jar /usr/local/src/hive/jars/udf.jar; 將jar文件放到分佈式緩存裏。
在這裏插入圖片描述
執行create temporary function my_lower as 'com.utstar.patrick.Lower';創建臨時的function
然後就像使用內置方法一樣使用my_lower方法。如select my_lower("ABc");
在這裏插入圖片描述
可以執行drop temporary function if exists my_lower;來刪除臨時的function

關於org.apache.hadoop.hive.ql.exec.UDF類更詳細的說明可以查看源碼
在這裏插入圖片描述

transform腳本

Hive的 TRANSFORM 關鍵字提供了在SQL中調用自寫腳本的功能,適合實現Hive中沒有的功能但是又不想寫UDF的情況。
關於TRANSFORM首先需要知道的是,所有的column在作爲參數傳入用戶自定義的transform腳本時都會預先轉換成字符串並且用TAB分隔。如果是NULL值的話會轉變成\N字符串以區別空字符串,腳本里標準的輸出也是用TAB分隔的字符串,如果是\N字符串會被hive當做NULL處理。

如下一個簡單的字符串拼接腳本。這個腳本和HadoopStreaming腳本非常類似的,都是通過標準輸入和輸出來處理數據。
注意需要用strip()來消除首尾的空格和換行符,否則打印時會有換行。

import sys

for line in sys.stdin:
    a, b = line.strip().split("\t")
    print " : ".join([a, b])

假如上述腳本文件保存爲 /usr/local/src/hive/jars/my.py
添加文件到分佈式緩存 add file /usr/local/src/hive/jars/my.py;

示例如下

select transform("name","patrick") using 'my.py' as str;

如下圖所示
在這裏插入圖片描述
我們以HIVE學習四:Window And Analytical Function中的orders表爲例將order_idcustomer_name拼接起來

select transform(order_id, customer_name) using 'my.py' as str from orders;

如下圖所示
在這裏插入圖片描述

lateral view

在說明這個lateral view,需要先了解下explode函數。
如下所示,explode函數的參數是array,該函數會將array轉變成多條數據。
在這裏插入圖片描述
split函數可以生成array
在這裏插入圖片描述
如圖 select split("1,2,3",",") as arr; 將字符串1,2,3按照逗號分割成了一個array
在這裏插入圖片描述
然後通過 select explode(split("1,2,3",",")) as arr; 將一個array變成了多條數據,成功地實現了行轉列。
在這裏插入圖片描述
在瞭解到explode之後我們接着看Lateral View
其完整語法如下,一般都與UDTF函數如explode聯合使用

lateralView: LATERAL VIEW udtf(expression) tableAlias AS columnAlias (',' columnAlias)*
fromClause: FROM baseTable (lateralView)*

假設我們有如下一張表pageads,其中add_list的類型是array<int>

create table pageads
(pageid string, adid_list array<int>);

初始化表pageads

insert into table pageAds
select "front_page", Array(1,2,3)
union
select "contact_page", Array(3,4,5);

查看錶數據
在這裏插入圖片描述
可以通過 show create table pageads;desc formatted pageads;查看錶的具體信息
在這裏插入圖片描述
在這裏插入圖片描述
然後我們想查看每個廣告在哪個頁面上出現過,可以執行如下語句

select pageid, adid from pageads lateral view explode(adid_list) temp as adid;

在這裏插入圖片描述
如果想查看每個廣告出現的次數,就可以執行如下語句

select count(*), adid from pageads lateral view explode(adid_list) temp as adid group by adid;

在這裏插入圖片描述

Multiple Lateral Views

一個from語句可以包含多個LATERAL VIEW語句。後面的LATERAL VIEW語句可以引用出現在LATERAL VIEW左邊的表的任何字段。
例如,下面sql查詢是正確的

SELECT * FROM exampleTable
LATERAL VIEW explode(col1) myTable1 AS myCol1
LATERAL VIEW explode(col2) myTable2 AS myCol2;

創建如下表

create table test
(col1 array<int>, col2 array<string>);

初始化數據如下

insert into table test
select Array(1,2), Array("a","b","c")
union
select Array(3,4), Array("d","e","f");

查看數據如下圖
在這裏插入圖片描述
然後執行下面的sql語句,包含多個lateral view

SELECT * FROM test
LATERAL VIEW explode(col1) myTable1 AS myCol1
LATERAL VIEW explode(col2) myTable2 AS myCol2;

結果如下圖
在這裏插入圖片描述

一個包容萬象的小例子

假設有這麼一張表stu

create table stu
(name string, info string);

表中有如下數據

insert into table stu
select "zhangsan","math:90,english:60"
union
select "lisi","chinese:80,math:66,english:77"
union
select "wangwu","chinese:66,math:55,english:80";

如下圖所示
在這裏插入圖片描述
現在要查詢每門課程中最高分和其對應的學生姓名
第一步,首先explode出學生、課程、分數的信息,sql如下

select transform(name, sub_grade) using 'split.py' as name,sub,grade 
from stu lateral view explode(split(info,",")) temp as sub_grade

在這裏插入圖片描述
注意一點transform腳本需要處理name, sub_grade,而不能像select name,transform(sub_grade) using 'split.py' as sub,grade from stu lateral view explode(split(info,",")) temp as sub_grade;一樣,簡單地說你所返回的數據要全部從transform腳本里輸出,而不能一部分從表引用,一部分從transform腳本里輸出。

其中transform腳本文件split.py內容如下

import sys

for line in sys.stdin:
    a, b = line.strip().split("\t")
    sub, grade = b.split(":")
    print "{}\t{}\t{}".format(a, sub, grade)

第二步,由於用transform腳本做了transformation處理,類型全部會變成string類型,具體可參考Setting Types for Sort By這篇我寫的博客,需要用cast函數將grade轉化爲int類型。

select name, sub, cast(grade as int) grade from
(
select transform(name, sub_grade) using 'split.py' as name,sub,grade from stu lateral view explode(split(info,",")) temp as sub_grade
) a

第三步,用窗口函數獲取每個課程的最高分及學生姓名

select *,first_value(grade) over (partition by sub order by grade desc) as g
,first_value(name) over (partition by sub order by grade desc) as n
from
(
select name, sub, cast(grade as int) grade from
(
select transform(name, sub_grade) using 'split.py' as name,sub,grade from stu lateral view explode(split(info,",")) temp as sub_grade
) a
) b

在這裏插入圖片描述
第四步,去重即可完成最終的sql

select distinct sub,first_value(grade) over (partition by sub order by grade desc) as g
,first_value(name) over (partition by sub order by grade desc) as n
from
(
select name, sub, cast(grade as int) grade from
(
select transform(name, sub_grade) using 'split.py' as name,sub,grade from stu lateral view explode(split(info,",")) temp as sub_grade
) a
) b

在這裏插入圖片描述

參考網址

HivePlugins
LanguageManual+Transform
LanguageManual+Commands
LanguageManual+UDF
LanguageManualDDL-CreateFunction
UDF簡單使用
how-can-select-a-column-and-do-a-transform-in-hive

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章