使用Python实现HIVE的UDF函数

开发 后端
UDF和SQL的区别在于,在处理复杂逻辑时候,UDF相比SQL能更高效地组织起来逻辑并落地实现功能。UDF和普通脚本的关键区别所在在于将 for line in f 替换成 for line in sys.stdin,常规函数一般是将文件一行行读入,UDF是从标准输入一行行加载数据。

[[390588]]

在处理一些复杂逻辑时候,python这种面向过程的语言相比于SQL更符合人的思维方式。相信有不少同学曾经感慨,如果能用python处理数据库中的数据就好了。那么今天它来了。

首先用python写处理复杂逻辑的自定义的函数(一阳指),再将函数代码嵌入SQL(狮吼功)就能合并成了一整招:UDF

下面我用一个栗子来说明一些两者处理数据过程中的差异,在介绍栗子之前,先介绍一些with as。与python 创建函数或者类一样,with as 用于创建中间表

简单来做个介绍

  1. select 
  2. from(select * from table where dt='2021-03-30')a 

可以写成

  1. with a as (select * from table where dt='2021-03-30' ) 
  2. select * from a 

简单的SQL看不出这样的优势(甚至有点多此一举),但是当逻辑复杂了之后我们就能看出这种语法的优势,他能从底层抽取中间表格,让我们只专注于当前使用的表格,进而可以将复杂的处理逻辑分解成简单的步骤。

如下面地表格记录了用户适用app过程中每个行为日志地时间戳,我们想统计一下用户今天用了几次app,以及每次的起始时间和结束时间是什么时候,这个问题怎么解呢?

SQL实现方式

首先用with as 构建一个中间表(注意看on 和 where条件)

  1. with t1 as 
  2. (select 
  3. x.uid, 
  4. case when x.rank=1 then y.timestamp_ms 
  5. else x.timestamp_ms 
  6. end as start_time, 
  7. case when x.rank=1 then x.timestamp_ms 
  8. else y.timestamp_ms end as end_time 
  9. from 
  10. (select 
  11. uid, 
  12. timestamp_ms, 
  13. row_number()over(partition by uid order by timestamp_ms) rank 
  14. from tmp.tmpx) x 
  15. left outer join 
  16. (select 
  17. uid, 
  18. timestamp_ms, 
  19. row_number()over(partition by uid order by timestamp_ms) rank 
  20. from tmp.tmpx) y 
  21. on x.uid=y.uid and x.rank=y.rank-1 
  22. where x.rank=1 or y.rank is null or y.timestamp_ms-x.timestamp_ms>=300) 

首先我们用开窗函数错位相减,用where条件筛选出我们需要的列,其中

x.rank=1 抽取出第一行

y.rank is null 抽取最后一样

y.timestamp_ms-x.timestamp_ms>=300抽取满足条件的行,如下:

当然这个结果并不是我们要的结果,需要将上述表格中某一行数据的end-time和下一条数据的start-time结合起来起来,构造出时间段

好的,按照上面我们所说的那么下面我们不用关心底层的逻辑,将注意力专注于这张中间表t1

  1. select 
  2. a.uid,end_time as start_time,start_time as end_time 
  3. from 
  4. (select uid,start_time,row_number()over(partition by uid order by start_time) as rank from t1) a 
  5. join 
  6. (select uid,end_time,row_number()over(partition by uid order by end_time) as rank from t1)b 
  7. on 
  8. a.uid=b.uid and a.rank=b.rank+1 

同样,排序后错位相减,然后就可以打完收工了~

UDF实现方式

首先我们假设上述数据存储在csv中,

用python 处理本地文件data.csv,按照python的处理方式写代码(这里就不一句句解释了,会python的同学可以跳过,不会的同学不妨自己动手写一下)

  1. def life_cut(files): 
  2. f=open(files) 
  3. act_list=[] 
  4. act_dict={} 
  5. for line in f: 
  6.     line_list=line.strip().split() 
  7.     key=tuple(line_list[0:1]) 
  8.     if key not in act_dict: 
  9.         act_dict.setdefault(key,[]) 
  10.         act_dict[key].append(line_list[1]) 
  11.     else
  12.         act_dict[key].append(line_list[1]) 
  13.  
  14. for k,v in act_dict.items(): 
  15.     k_str=k[0]+"\t" 
  16.     start_time = v[0] 
  17.     last_time=v[0] 
  18.     i=1 
  19.     while i<len(v)-1: 
  20.         if int(v[i])-int(last_time)>=300: 
  21.             print(k_str+"\t"+start_time+"\t"+v[i-1]) 
  22.             start_time=v[i] 
  23.             last_time = v[i] 
  24.             i=i+1 
  25.         else
  26.             last_time = v[i] 
  27.             i=i+1 
  28.     print(k_str+"\t"+start_time+"\t"+v[len(v)-1]) 
  29.     # print(k_str + "\t" + start_time + "\t" + v[i]) 
  30. if __name__=="__main__"
  31. life_cut("data.csv"

得到结果如下:


那么下面我们将上述函数写成udf的形式:

  1. #!/usr/bin/env python 
  2. # -*- encoding:utf-8 -*- 
  3. import sys 
  4. act_list=[] 
  5. act_dict={} 
  6. for line in sys.stdin: 
  7. line_list=line.strip().split("\t"
  8. key=tuple(line_list[0:1]) 
  9. if key not in act_dict: 
  10.     act_dict.setdefault(key,[]) 
  11.     act_dict[key].append(line_list[1]) 
  12. else
  13.     act_dict[key].append(line_list[1]) 
  14.  
  15. for k,v in act_dict.items(): 
  16. k_str=k[0]+"\t" 
  17. start_time = v[0] 
  18. last_time=v[0] 
  19. i=1 
  20. while i<len(v)-1: 
  21.     if int(v[i])-int(last_time)>=300: 
  22.       print(k_str+"\t"+start_time+"\t"+v[i-1]) 
  23.       start_time=v[i] 
  24.       last_time = v[i] 
  25.       i=i+1 
  26.     else
  27.       last_time = v[i] 
  28.       i=i+1 
  29. print(k_str+"\t"+start_time+"\t"+v[len(v)-1]) 

这个变化过程的关键点是将 for line in f 替换成 for line in sys.stdin,其他基本上没什么变化

然后我们再来引用这个函数

先add这个函数的路径add file /xxx/life_cut.py 加载udf路径,然后再使用

  1. select 
  2. TRANSFORM (uid,timestamp_ms) USING "python life_cut.py" as (uid,start_time,end_time) 
  3. from tmp.tmpx 

总结

从上述案例我们可以看出,

UDF和SQL的区别在于,在处理复杂逻辑时候,UDF相比SQL能更高效地组织起来逻辑并落地实现功能。UDF和普通脚本的关键区别所在在于将 for line in f 替换成 for line in sys.stdin,常规函数一般是将文件一行行读入,UDF是从标准输入一行行加载数据。希望大家平时没事的时候好好练练python,切莫书到用时方恨少。

 

责任编辑:姜华 来源: 数师兄
相关推荐

2023-05-06 07:15:59

Hive内置函数工具

2020-12-31 05:37:05

HiveUDFSQL

2011-06-20 09:52:56

MySQL

2010-02-02 17:33:35

Python函数编译

2010-02-03 15:40:37

Python函数

2021-12-13 07:57:47

Flink SQL Flink Hive Udf

2022-03-28 07:43:28

jsonHive数据库

2021-05-28 08:52:45

Hive分析函数

2010-11-25 11:57:42

MySQL查询行号

2021-08-11 07:02:04

Python激活函数

2022-03-31 07:32:33

Hivejson解析函数

2023-11-23 19:30:35

Python编程语言

2009-12-10 16:40:04

PHP处理分页

2023-03-20 08:14:11

PHP类型转换

2021-03-27 10:54:34

Python函数代码

2021-07-30 05:00:04

Python初等函数

2021-07-27 05:04:12

python初等函数

2017-07-10 08:30:11

Spark UDF变长参数

2010-03-15 10:49:57

Python函数变量

2018-11-15 10:20:59

Python函数式编程编程语言
点赞
收藏

51CTO技术栈公众号