问题
session_id | hour |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | 5 |
1 | 6 |
1 | 8 |
对于每一个连续的小时块,求每个块的结束时间。
如上事例数据可以分为 [1, 3], [5, 6], [8]
三块,每个块的结束时间分别是 3, 6, 8
实现 1
这个问题与 https://leetcode.cn/problems/merge-intervals/ 合并区间问题类似。
可以通过对数组一次遍历解决。自然我们可以通过 UDF 实现这个遍历。然后将 group by 后的 hour 列穿入其中。得到类似下面的 SQL
SELECT
session_id,
group_hour_get_max(hour)
FROM T
GROUP BY session_id
实现 2
这是在工作中看到的实现。不通过 group by 而是直接遍历每行,通过窗口函数 lag/lead 来获取前后元素。SQL 如下
SELECT
session_id,
id
FROM T
QUALIFY
LEAD(id) OVER (PARTITION BY sid ORDER BY id) - id > 1
-- or next_id is null
逻辑上:我们如果下一行目标列,跟本行的目标列的差大于阈值,我们就留下这一行。对于 [1, 2, 3]
1 和 2 与下一行的差都为1,所以被过滤掉只剩下 3
其他问题
我们可能想对还未结束的块特殊处理。根据如何判断块的结束有两种实现方式
- 下一个块的出现(差超过阈值)代表本块的结束 -> 那我们应该总放弃掉最后一个块
- 有空缺即代表结束 -> 如果最后一个块的最大值 < 源数据的最大值,说明有空缺的
总结
实现2用 SQL 的方式模拟了数组的遍历,个人认为是一个非常没必要的复杂度。但是避免了 UDF 也减少了一些维护成本。是一个可以思考的逻辑。
从性能上讲,由于窗口函数的存在,两种实现都需要 shuffle 。
从可读性上讲,实现1逻辑更符合直觉。
从扩展性上讲,实现2的缺点在于没法随着遍历维护状态,所有的状态都必须跟行绑定。如果需要包含之前所有元素的状态,比如之前的最小值,那需要窗口穿入之前所有数据,增加了增加了时间复杂度。