大数据高频考题:Hive开窗函数详解和最全案例 (滴滴出行、歌尔、快手面经)
先说说开窗函数是个啥吧。简单点讲,开窗函数(Window Function)是Hive SQL里一种特殊的函数,它能在不改变数据行数的情况下,对一组相关的数据行进行计算。啥意思呢?就是说,普通的聚合函数比如SUM、AVG啥的,用了之后会把多行数据聚合成一行结果,但开窗函数不会,它会保留每一行的原始数据,同时还能基于某个“窗口”(也就是一组相关的行)做计算。
为啥说它在大数据处理中重要呢?大数据的特点就是量大、维度多、逻辑复杂。咱们在分析数据的时候,经常会遇到一些场景,比如要对用户行为数据按时间排序后计算累计值,或者要给每个部门的员工排名,或者要找出每个分组里的前N个记录啥的。这些需求如果用传统的SQL写法,可能会涉及到大量的子查询、自连接,代码写得又臭又长,性能还差得一塌糊涂。而开窗函数的出现,就像是给咱们递上了一把瑞士军刀,能以更简洁、更高效的方式解决这些问题。
再举个例子,大家感受下。假设你是个电商平台的数据分析师,手头有张订单表,里面有用户ID、订单时间、订单金额等字段。你想算出每个用户从第一笔订单到当前订单的累计消费金额。如果不用开窗函数,你可能得先写个子查询,挨个去算每个订单之前的总和,逻辑复杂不说,数据量一大,跑SQL的时间能让你怀疑人生。但用了开窗函数,比如Hive里的SUM() OVER(),几行代码就能搞定,效率还高得飞起。是不是很香?
说到应用场景,开窗函数真的是无处不在。咱们可以拿它做排名,比如用RANK()或者DENSE_RANK()给数据按某个字段排序,找出Top N;也可以做聚合计算,比如算某个分组内的平均值、最大值啥的;还能处理时间序列数据,比如算移动平均、累计值,甚至是前后行的差值。像金融领域分析股票价格趋势、电商领域计算用户留存率、游戏行业分析玩家行为轨迹,这些场景里开窗函数都能大显身手。
具体点说,比如在电商场景中,你可能需要分析用户的购买频次和金额分布,找出“VIP用户”群体,这时候可以用RANK() OVER()按金额排名,挑出前10%的用户;在广告投放分析中,你可能想知道每个广告位的点击率变化趋势,可以用开窗函数结合时间窗口,算出每天的累计点击量和转化率。总之,不管你是做业务报表,还是搞机器学习的数据预处理,开窗函数都能帮你省不少事儿。
假设有张销售数据表sales,字段有region(区域)、salesperson(销售员)、amount(销售额),咱们想给每个区域的销售员按销售额排名。SQL可以这么写:
SELECT region, salesperson, amount, RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rankFROM sales;
这段代码啥意思呢?PARTITION BY region就是按区域分组,ORDER BY amount DESC是组内按销售额从高到低排序,RANK()就是给每组的销售员排名。结果出来后,每一行数据都会多一列rank,告诉你这个销售员在自己区域里的排名是多少。是不是很简单?如果没开窗函数,你可能得写一堆嵌套查询,累得半死。
再看看一个聚合的例子。还是这张表,咱想知道每个销售员的销售额在自己区域里的占比咋样,可以用:
SELECT region, salesperson, amount, amount / SUM(amount) OVER (PARTITION BY region) AS ratioFROM sales;
这儿SUM(amount) OVER (PARTITION BY region)算的是每个区域的总销售额,然后用当前行的amount除以这个总和,就得到了占比。数据直观又清晰,业务分析时特别有用。
当然啦,开窗函数的功能远不止这些。Hive里的开窗函数种类很多,有排名函数、值函数、聚合函数等等,每种函数还有不同的窗口定义方式,比如行范围、时间范围啥的。不同组合能玩出很多花样,满足各种复杂需求。
说到这儿,相信大家对开窗函数的重要性已经有了一定了解。它不只是个工具,更像是大数据分析中的一个“超级助手”,能帮咱们以更少的代码、更高的效率解决复杂问题。尤其是在Hive这种分布式查询引擎里,数据量动辄几百GB甚至TB,开窗函数带来的性能优化和逻辑简化,真的能救命。
顺带提一句,开窗函数虽然好用,但也不是万能的。有些场景下用错了窗口定义,或者数据分布不均,可能会导致性能问题。所以,学会用是一方面,学会优化又是另一方面。
第一章:Hive开窗函数的基础知识
如果你已经在大数据处理的坑里摸爬滚打了一段时间,那你肯定知道,处理数据时经常会遇到一些棘手的问题,比如怎么在一个数据集里既关注整体趋势,又能抓住每一行的细节?传统SQL里,咱可能得写一堆子查询或者自连接,代码长得跟小说似的,跑起来还慢得要命。而Hive的开窗函数,简直就是救命稻草,逻辑清晰,效率也高。
啥是开窗函数?
开窗函数,英文叫Window Function,简单来说,就是一种能在查询结果中对数据进行“分组计算”的工具,但又不会像GROUP BY那样把数据行给合并掉。换句话说,它能在每一行数据上,基于你定义的一个“窗口”范围,去计算一些统计值,比如排名、累计和、平均值啥的。这窗口可以是当前行的前后几行,也可以是整个分组的数据,灵活得很。
为啥叫“开窗”呢?想象一下,你在看一栋楼的窗户,每扇窗户都能看到一部分风景,但你人还站在原地没动。开窗函数也是这样,数据行没变,但通过“窗口”,你能看到更多相关信息。这跟传统的聚合函数(比如SUM、AVG)有啥区别?聚合函数一用,数据就直接被压缩成一行或者几行了,而开窗函数是每一行都能保留,同时还能附加上计算结果。
在Hive里,开窗函数通常跟OVER子句一起用,OVER就是定义窗口的范围和规则的。接下来,咱们就拆解一下它的具体语法和用法。
开窗函数的基本语法
Hive里的开窗函数语法结构不算复杂,但细节挺多,搞清楚了用起来就顺手。基本格式长这样:
<函数名>(<列名>) OVER ( [PARTITION BY 列名1, 列名2, ...] [ORDER BY 列名3, 列名4, ...] [窗口范围定义])
这里面有几个关键部分:
- 函数名:就是你要用的具体开窗函数,比如RANK()、SUM()、AVG()、COUNT()等等。Hive支持的开窗函数种类不少,排名类的有RANK()、DENSE_RANK()、ROW_NUMBER(),聚合类的有SUM()、AVG()、MAX()、MIN(),还有一些时间序列相关的像LAG()、LEAD(),后续会详细聊。
- OVER子句:这是开窗函数的核心,定义了窗口的范围和规则。OVER里面可以有三个主要部分:PARTITION BY、ORDER BY和窗口范围。
- PARTITION BY:类似于GROUP BY,作用是把数据按某个或某些列分成不同的组。不同的是,PARTITION BY不会把数据合并,而是保留每一行,只是在计算时限制窗口范围到同一个组内。
- ORDER BY:定义窗口内数据的排序方式,决定了计算的顺序。比如排名函数或者累计求和,排序规则直接影响结果。
- 窗口范围定义:这个是指定窗口的具体范围,比如是从当前行往前几行,还是到后面几行,或者整个组。常用的有ROWS BETWEEN啥的,后面会举例。
光看语法可能有点晕,直接上个简单例子感受下。假设有个销售表sales,里面有订单ID、销售员名字和销售额,数据长这样:
1 |
Alice |
500 |
2 |
Alice |
300 |
3 |
Bob |
700 |
4 |
Bob |
200 |
我想知道每个销售员的销售额排名,咋办?用RANK()函数试试:
SELECT order_id, salesman, amount, RANK() OVER (PARTITION BY salesman ORDER BY amount DESC) AS rankFROM sales;
结果会是:
1 |
Alice |
500 |
1 |
2 |
Alice |
300 |
2 |
3 |
Bob |
700 |
1 |
4 |
Bob |
200 |
2 |
看到没?PARTITION BY salesman把数据按销售员分组,ORDER BY amount DESC让每个组内按销售额从高到低排序,RANK()函数就给每行算出了组内的排名,但原始数据一行没少。这就是开窗函数的魅力。
窗口定义的细节:PARTITION BY和ORDER BY
再深挖一下PARTITION BY和ORDER BY这两个核心玩意儿。PARTITION BY的作用就像是把数据切成小块儿,每个小块儿独立计算。比如上面例子,Alice和Bob各自是一个“分区”,排名只在自己分区内算,不会跨组。如果你不写PARTITION BY,那整个表就是一个大窗口,所有数据一起算。
ORDER BY则是决定窗口内数据怎么排队的,尤其对排名函数和累计计算函数影响巨大。比如你用SUM() OVER()算累计和,ORDER BY决定了是从第一行累加到当前行,还是反过来。如果没写ORDER BY,Hive会默认按表原来的顺序,但这往往不是你想要的,所以一般都得显式指定。
举个例子,还是上面的sales表,我想算每个销售员从第一笔订单到当前订单的累计销售额:
SELECT order_id, salesman, amount, SUM(amount) OVER (PARTITION BY salesman ORDER BY order_id) AS cumulative_amountFROM sales;
结果是:
1 |
Alice |
500 |
500 |
2 |
Alice |
300 |
800 |
3 |
Bob |
700 |
700 |
4 |
Bob |
200 |
900 |
这里ORDER BY order_id让累计和按订单ID顺序来,Alice的第二笔订单就把前两笔加起来了,Bob也是类似。
窗口范围:ROWS BETWEEN的玩法
除了分区和排序,窗口范围的定义也很重要。默认情况下,窗口范围是从分区开始到当前行(对于有序窗口),但你可以用ROWS BETWEEN手动指定范围。常用的有以下几种:
- UNBOUNDED PRECEDING:从分区第一行开始。
- CURRENT ROW:当前行。
- UNBOUNDED FOLLOWING:到分区最后一行。
- n PRECEDING/FOLLOWING:当前行往前或往后n行。
比如,我想算当前行加上前一行的销售额总和,可以这么写:
SELECT order_id, salesman, amount, SUM(amount) OVER ( PARTITION BY salesman ORDER BY order_id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) AS sum_with_prevFROM sales;
结果大概是这样:
1 |
Alice |
500 |
500 |
2 |
Alice |
300 |
800 |
3 |
Bob |
700 |
700 |
4 |
Bob |
200 |
900 |
第一行没前一行,所以就只有自己的值,第二行开始就把前一行的加上了。灵活调整窗口范围,能满足很多复杂需求。
开窗函数的运行原理
聊完语法,咱再简单说说开窗函数咋工作的。其实Hive在执行开窗函数时,会先根据PARTITION BY把数据分好组,然后在每个组内按ORDER BY排序,最后根据窗口范围去计算每一行的结果。这过程有点像MapReduce的分组和排序,先把数据分片处理,再逐个计算。
有一点要注意,开窗函数是作用在查询结果上的,也就是说,它是在WHERE、GROUP BY、HAVING这些过滤和聚合之后执行的,但又在最后的ORDER BY之前。所以你不能在WHERE里直接用开窗函数的结果去过滤,得套个子查询。比如你想找排名前3的销售记录,得这么干:
SELECT *FROM ( SELECT order_id, salesman, amount, RANK() OVER (PARTITION BY salesman ORDER BY amount DESC) AS rank FROM sales) tmpWHERE rank <= 3;
常见开窗函数分类和用途
Hive支持的开窗函数大致分三类,简单提一下,方便你有个印象:
1. 排名函数:用来算排名的,像RANK()、DENSE_RANK()、ROW_NUMBER()。RANK()遇到相同值会跳号,比如1,1,3;DENSE_RANK()不会跳,比如1,1,2;ROW_NUMBER()是唯一序号,不管值是否相同。
2. 聚合函数:SUM()、AVG()、COUNT()、MAX()、MIN()这些,配合OVER子句,能算分组内的统计值。
3. 值函数:LAG()取前一行值,LEAD()取后一行值,FIRST_VALUE()取组内第一个值,LAST_VALUE()取最后一个值,适合时间序列分析。
第二章:Hive开窗函数的分类与功能
Hive的开窗函数是个相当给力的工具,能在处理大数据时帮我们解决不少复杂问题。它的强大之处在于,可以在不改变数据行的情况下,对指定范围内的数据进行各种计算,既能看到整体趋势,又能保留每一行的细节。
1. 排名函数:给数据排个序
排名函数是开窗函数里最常用的一类,主要用来给数据行分配一个排名值。它们特别适合用在需要排序、筛选或者分析名次的场景,比如电商平台分析商品销量排名,或者游戏数据里看玩家的积分排行。Hive里常见的排名函数有ROW_NUMBER()、RANK()DENSE_RANK(),咱们一个个来看。
- ROW_NUMBER():这个函数会给每一行分配一个唯一的序号,按照你定义的排序规则来编号。比如你想按销售额从高到低给商品排个序,哪怕销售额相同,每一行都会有个不一样的序号。假设咱们有张销售表,字段有、和,想按地区分组,然后按销售额降序排名,可以这么写:
SELECT product_id, region, sales_amount, ROW_NUMBER() OVER (PARTITION BY region ORDER BY sales_amount DESC) AS rank FROM sales;
运行这段代码,同一里的商品会按销售额从高到低排列,每个商品有个唯一的排名,哪怕销售额一样,排名也不会重复。场景上,这种函数特别适合用来分页查询,或者提取每个分组的前几名数据。
- RANK():和ROW_NUMBER()不同,RANK()`碰到相同值时会给相同的排名,但下一个排名会跳过。比如销售额并列第一的两个商品都排1,那下一个商品直接排3。这种特性在某些场景下更符合直觉,比如评选“最佳销售员”,并列第一的情况不需要硬分个高下。还是用上面的表,代码可以改成:
SELECT product_id, region, sales_amount, RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) AS rank FROM sales;
这里如果两个商品销售额一样,排名就是一样的,但总排名数会少一些。这种方式适合用在需要关注“实际名次”的场景,比如比赛排名。
- DENSE_RANK():这个函数和RANK()很像,但它不会跳过排名。还是并列第一的情况,两个商品都排1,但下一个商品会排2,而不是3。代码改动不大:
SELECT product_id, region, sales_amount, DENSE_RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) AS rank FROM sales;
这种排名方式更适合用在需要连续排名的场景,比如你想知道一个组内有多少个“不同名次”的情况,`DENSE_RANK()`就很合适。
这三者选哪个,主要看你的业务需求。如果需要唯一序号,选`ROW_NUMBER()`;如果并列排名后要跳过序号,选`RANK()`;如果并列后还想要连续排名,那就用`DENSE_RANK()`。实际工作中,我发现电商和游戏行业用得最多的是`ROW_NUMBER()`,因为分页和Top N查询需求太常见了。
2. 聚合函数:窗口内的统计计算
除了排名,Hive的开窗函数还能做聚合计算,比如求和、平均值、计数等。传统聚合函数像SUM()、AVG()会把多行数据合并成一行,但开窗函数版本的聚合函数可以在每行上附加计算结果,数据行本身不变。这点在分析时序数据或者分组统计时特别有用。常见的聚合函数有SUM()、AVG()、COUNT()、MAX()、MIN()。
- SUM():窗口内的求和函数,特别适合用来计算累计值。比如你有张订单表,字段有、和,想按用户分组,计算每个订单的累计金额,可以这么写:
SELECT user_id, order_date, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS cumulative_amount FROM orders;
这样每一行都能看到从第一个订单到当前订单的金额总和。场景上,这种累计计算在财务分析、用户行为分析里很常见,比如计算用户的历史消费总额。
- AVG():窗口内的平均值计算,用来分析趋势。比如你想知道每个用户的订单金额平均值如何变化,可以用类似代码:
SELECT user_id, order_date, amount, AVG(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS avg_amount FROM orders;
这种方式能帮你看出用户消费水平的波动,适合用在用户画像或者行为预测里。
- COUNT():统计窗口内的行数。比如你想知道每个用户到当前订单为止一共下了多少单,代码可以是:
SELECT user_id, order_date, amount, COUNT(*) OVER (PARTITION BY user_id ORDER BY order_date) AS order_count FROM orders;
这个在分析用户活跃度时很有用,比如看看用户是“一次性消费”还是“持续购买”。
聚合函数的窗口范围可以通过子句灵活调整,比如默认是从第一行到当前行,也可以设置成整个分组,或者前后几行。实际工作中,我常用这些函数来做时间序列分析,比如计算近7天的平均销售额,直接在窗口范围里限制就行,超级方便。
3. 值函数:访问前后行的数据
值函数是开窗函数里另一类很有特色的功能,主要用来访问窗口内其他行的数据,而不需要通过自连接或者子查询。这类函数包括`LAG()`、`LEAD()`、`FIRST_VALUE()LAST_VALUE()`,在处理时序数据或者需要对比前后记录时特别好使。
- LAG():获取当前行上一行的某个字段值。比如你想知道每个订单和上一个订单的金额差值,可以这么写:
SELECT user_id, order_date, amount, LAG(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS prev_amount, amount - LAG(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS amount_diff FROM orders;
这里就是上一个订单的金额,就是差值。这种函数在分析用户消费间隔变化时特别有用,比如看看用户是不是越买越贵。
- LEAD():和LAG()相反,获取下一行的值。场景类似,比如想知道下个订单金额是多少,直接用`LEAD()`就行。代码就不多写了,改个函数名而已。
- FIRST_VALUE() 和 LAST_VALUE():分别获取窗口内第一行和最后一行的值。比如你想知道每个用户第一个订单的金额,可以用:
SELECT user_id, order_date, amount, FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS first_order_amount FROM orders;
这在分析用户初始行为或者最终行为时很有用,比如对比用户第一个订单和最后一个订单,看看消费习惯有啥变化。
值函数的亮点在于,它们能轻松实现前后行的对比,而不需要写复杂的子查询或者自连接。在大数据场景下,这种方式效率高很多。我之前处理过一个用户行为日志的项目,用`LAG()LEAD()`分析用户操作的上下步骤,直接省了好多代码,跑起来也快。
4. 分类小结与实用技巧
上面咱们聊了排名函数、聚合函数和值函数三大类,每一类都有自己擅长的场景。排名函数适合排序和名次分析,聚合函数适合统计和趋势计算,值函数则适合时序数据的对比和提取。要是用一张表来总结,大概是这样的:
排名函数 |
ROW_NUMBER(), RANK() |
分配排名 |
分页、Top N、比赛排名 |
聚合函数 |
SUM(), AVG(), COUNT() |
窗口内统计 |
累计值、平均值、趋势分析 |
值函数 |
LAG(), LEAD(), FIRST_VALUE() |
访问前后行数据 |
时序对比、行为分析 |
用的时候,记得灵活调整窗口范围,比如`ORDER BYROWS BETWEEN`可以限制计算范围,像是`ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING`就是前后各一行的范围,特别适合做局部统计。
另外,Hive的开窗函数虽然强大,但大数据量下性能可能是个问题。我的经验是,尽量在`PARTITION BY`里用高选择性的字段分组,减少每个窗口的数据量,不然计算时间会飙升。还有,Hive版本不同,支持的函数和语法可能有差异,老版本可能不支持某些窗口范围设置,跑代码前最好查查文档。
5. 一个综合案例
为了把这些函数串起来,咱们来看个稍微复杂点的例子。假设有张学生成绩表,字段有、、和,想分析每个班级的学生成绩排名、累计平均分和上次考试的成绩对比。代码可以这么写:
SELECT student_id, class_id, exam_date, score, ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS class_rank, AVG(score) OVER (PARTITION BY class_id ORDER BY exam_date) AS cumulative_avg, LAG(score) OVER (PARTITION BY student_id ORDER BY exam_date) AS last_score, score - LAG(score) OVER (PARTITION BY student_id ORDER BY exam_date) AS score_diffFROM scores;
这段代码里,`ROW_NUMBER()`算班级排名,`AVG()`算累计平均分,`LAG()`拿上次成绩,最后还算了个成绩变化值。跑出来结果能直观看出每个学生在班级里的表现、整体趋势和个人进步情况,分析起来特别全面。
第三章:Hive开窗函数的窗口范围与边界控制
Hive开窗函数的强大之处,不仅仅在于它能帮我们计算排名或者聚合数据,更在于它能让我们灵活地定义“窗口”——也就是计算的范围。啥叫窗口范围?简单来说,就是你想让函数在哪些数据行上做计算,是当前行往上数几行,还是往下看几行,甚至是整个数据集。掌握了窗口范围和边界的控制,你就能随心所欲地玩转开窗函数,解决各种复杂的数据分析需求。
窗口范围的基本概念
在Hive的开窗函数中,窗口范围决定了函数计算时会考虑哪些数据行。如果不显式指定范围,Hive会默认把整个分区(由`PARTITION BY`划分的数据组)都当做窗口范围。但现实中,我们往往不需要整个分区的数据,而是只想聚焦于当前行附近的某几行数据。这时候,就得靠`ROWS BETWEEN`来定义窗口的起始和结束位置。
ROWS BETWEEN的语法大致是这样的:
<函数>() OVER ( PARTITION BY 列名 ORDER BY 列名 ROWS BETWEEN 起始边界 AND 结束边界)
这里的`ROWS BETWEEN`可以指定窗口的上下边界,比如从“当前行之前的第几行”到“当前行之后的第几行”。边界可以用几种方式表示:`UNBOUNDED PRECEDING`(从分区开头到当前行)、`CURRENT ROW`(当前行)、`UNBOUNDED FOLLOWING`(从当前行到分区结尾),或者直接用数字表示相对位置,比如`5 PRECEDING`(当前行前5行)。
为啥要控制窗口范围?因为不同的范围会直接影响计算结果。举个例子,计算累计销售额时,你是想从第一笔订单累加到当前行,还是只看最近三天的订单?不同的业务场景,需要不同的窗口定义。
窗口边界的几种常见设置
接下来,咱们逐一拆解几种常见的窗口边界设置,看看它们咋用,效果咋样。
1. 从分区开头到当前行
这种设置是最常见的,适合做累计计算,比如累计销售额、累计访问量啥的。语法上就是`ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`。
举个栗子,假设我们有个销售表,字段有(订单日期)、(产品名)、(销售额)。我想算每个产品的累计销售额,按日期排序:
SELECT order_date, product, amount, SUM(amount) OVER ( PARTITION BY product ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_sales FROM sales;
这段代码的意思是,对每个产品分区,按日期排序,从第一笔订单开始累加销售额到当前行。结果表会显示每笔订单对应的累计值,方便看出销售趋势。
2. 当前行到分区结尾
反过来,如果想从当前行累加到最后一行咋办?可以用`ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING`。这种场景适合预测或者反向累计,比如算“剩余库存”之类的。
还是用上面的表,假设我想知道从当前订单开始到最后,剩余的总销售额:
SELECT order_date, product, amount, SUM(amount) OVER ( PARTITION BY product ORDER BY order_date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ) AS remaining_sales FROM sales;
这样,每一行显示的就是从当前订单到最后的所有销售额之和。
3. 固定范围的窗口
有时候,我们不想要整个分区的数据,只想看当前行附近的数据,比如前3行到后3行。这时候可以用数字指定相对位置,像`ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING`。
假设我想算每个订单的“周边7天销售额均值”(前3天+当前+后3天),可以这么写:
SELECT order_date, product, amount, AVG(amount) OVER ( PARTITION BY product ORDER BY order_date ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING ) AS avg_nearby_sales FROM sales;
这样,每一行对应的均值就是基于它前后各3天的销售额算出来的,适合平滑数据波动,分析局部趋势。
4. 只看当前行
如果窗口范围设为`ROWS BETWEEN CURRENT ROW AND CURRENT ROW`,那就只计算当前行本身的数据。这种情况用得少,但偶尔有用,比如结合其他逻辑做标记啥的。
窗口范围对结果的影响
说了这么多定义方法,窗口范围到底咋影响计算结果呢?咱们用一个具体的例子,跑几组不同的范围设置,看看结果有啥区别。
假设有张表,记录用户每天的访问次数,字段是。数据如下:
1 |
2023-01-01 |
5 |
1 |
2023-01-02 |
3 |
1 |
2023-01-03 |
7 |
1 |
2023-01-04 |
2 |
1 |
2023-01-05 |
4 |
现在我想算每个用户的累计访问次数,但用不同的窗口范围,看看结果咋变。
- 范围1:从开头到当前行
代码:
SELECT user_id, visit_date, visit_count, SUM(visit_count) OVER ( PARTITION BY user_id ORDER BY visit_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_visits FROM user_visits;
结果:
1 |
2023-01-01 |
5 |
5 |
1 |
2023-01-02 |
3 |
8 |
1 |
2023-01-03 |
7 |
15 |
1 |
2023-01-04 |
2 |
17 |
1 |
2023-01-05 |
4 |
21 |
累计值是逐步增加的,符合咱们的预期。
- 范围2:前1行到后1行
代码:
SELECT user_id, visit_date, visit_count, SUM(visit_count) OVER ( PARTITION BY user_id ORDER BY visit_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) AS nearby_visits FROM user_visits;
结果:
1 |
2023-01-01 |
5 |
8 |
1 |
2023-01-02 |
3 |
15 |
1 |
2023-01-03 |
7 |
12 |
1 |
2023-01-04 |
2 |
13 |
1 |
2023-01-05 |
4 |
6 |
看到没?第一行只有当前+后1行,所以是5+3=8;中间的行是前1+当前+后1,比如第三行是3+7+2=12;而最后一行只有前1+当前,是2+4=6。范围小了,结果波动就更明显。
- 范围3:当前行到结尾
代码:
SELECT user_id, visit_date, visit_count, SUM(visit_count) OVER ( PARTITION BY user_id ORDER BY visit_date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ) AS remaining_visits FROM user_visits;
结果:
1 |
2023-01-01 |
5 |
21 |
1 |
2023-01-02 |
3 |
16 |
1 |
2023-01-03 |
7 |
13 |
1 |
2023-01-04 |
2 |
6 |
1 |
2023-01-05 |
4 |
4 |
这回是从当前行累加到最后,所以数值是递减的,反映了“剩余”的访问量。
通过这三组对比,相信你能感觉到窗口范围对结果的影响有多大。选择合适的范围,得结合具体的业务场景,比如想看整体趋势就用大范围,想关注局部波动就缩小窗口。
窗口范围设置的注意事项
在实际操作中,设置窗口范围还有几点需要注意,不然容易踩坑。
1. 边界超出分区咋办
如果你设置的范围超出了分区边界,比如`3 PRECEDING`但当前行前面只有2行,Hive不会报错,而是只取实际存在的行进行计算。同样,超出尾部时也只取到最后一行。这点挺人性化,但得心里有数,不然结果可能跟你预期不一致。
2. 排序对窗口的影响
窗口范围的计算是基于ORDER BY后的顺序的,如果没指定排序,Hive会按默认顺序处理,但结果可能乱七八糟。所以,定义窗口范围时,ORDER BY几乎是必须的,尤其涉及时间序列或者排名场景。
3. 性能问题
窗口范围越大,计算量越大,尤其是这种涉及整个分区的设置,在大数据场景下可能会很慢。如果数据量大,尽量缩小范围,或者通过分区把数据拆小再计算。
实际案例:电商订单分析
最后,咱们来个综合点的案例,把窗口范围的玩法用起来。假设有个电商订单表,字段有、、,我想分析每个用户的订单趋势,包括累计订单金额、最近3笔订单均值、未来订单总和。代码如下:
SELECT user_id, order_date, order_amount, SUM(order_amount) OVER ( PARTITION BY user_id ORDER BY orde
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
17年+码农经历了很多次面试,多次作为面试官面试别人,多次大数据面试和面试别人,深知哪些面试题是会被经常问到。 在多家企业从0到1开发过离线数仓实时数仓等多个大型项目,详细介绍项目架构等企业内部秘不外传的资料,介绍踩过的坑和开发干货,分享多个拿来即用的大数据ETL工具,让小白用户快速入门并精通,指导如何入职后快速上手。 计划更新内容100篇以上,包括一些企业内部秘不外宣的干货,欢迎订阅!