如何为不同的观察组使用 LAG 和 LEAD 窗口函数

Posted

技术标签:

【中文标题】如何为不同的观察组使用 LAG 和 LEAD 窗口函数【英文标题】:How to use LAG & LEAD window functions for different groups of observations 【发布时间】:2019-12-02 13:48:24 【问题描述】:

这是一个关于使用 Spark SQL 在 Databricks 上使用 LAG 和 LEAD 窗口函数的问题,但我认为该问题不一定与特定的 SQL 方言有关。

我有一个列出不同客户 (ID) 访问的输入表和一个指示“特殊访问”的标志:

ID | date       | special_visit
-------------------------------
A  | 2018-01-01 | 0            
A  | 2018-02-01 | 1            
A  | 2018-03-01 | 1            
B  | 2018-01-02 | 0            
B  | 2018-02-02 | 0            
B  | 2018-03-02 | 1             

我想要创建的是下表:

ID | date       | special_visit | prev_visit | next_visit | prev_special_visit | next_special_visit
---------------------------------------------------------------------------------------------------
A  | 2018-01-01 | 0             | NULL       | 2018-02-01 | NULL               | 2018-02-01
A  | 2018-02-01 | 1             | 2018-01-01 | 2018-03-01 | NULL               | 2018-03-01  
A  | 2018-03-01 | 1             | 2018-02-01 | NULL       | 2018-02-01         | NULL
B  | 2018-01-02 | 0             | NULL       | 2018-02-02 | NULL               | 2018-03-02
B  | 2018-02-02 | 0             | 2018-01-02 | 2018-03-02 | NULL               | 2018-03-02
B  | 2018-03-02 | 1             | 2018-02-02 | NULL       | NULL               | NULL

对于每次访问,它都会向我显示下一次/上一次访问(每次特殊访问也算作“正常”访问)以及每个 ID 的下一次/上一次特殊访问。

到目前为止我得到的是以下输出:

ID | date       | special_visit | prev_visit | next_visit | prev_special_visit | next_special_visit
---------------------------------------------------------------------------------------------------
A  | 2018-01-01 | 0             | NULL       | 2018-02-01 | NULL               | NULL
A  | 2018-02-01 | 1             | 2018-01-01 | 2018-03-01 | NULL               | 2018-03-01  
A  | 2018-03-01 | 1             | 2018-02-01 | NULL       | 2018-02-01         | NULL
B  | 2018-01-02 | 0             | NULL       | 2018-02-02 | NULL               | NULL
B  | 2018-02-02 | 0             | 2018-01-02 | 2018-03-02 | NULL               | NULL
B  | 2018-03-02 | 1             | 2018-02-02 | NULL       | NULL               | NULL

使用此查询:

WITH special_visits AS (
SELECT  ID
       ,date
       ,LAG(date) OVER (PARTITION BY ID ORDER BY date) AS prev_special_visit
       ,LEAD(date) OVER (PARTITION BY ID ORDER BY date) AS next_special_visit
FROM input
WHERE special_visit = 1)

SELECT ID
      ,special_visit
      ,LAG(date) OVER (PARTITION BY ID ORDER BY date) AS prev_visit
      ,LEAD(date) OVER (PARTITION BY ID ORDER BY date) AS next_visit
      ,special_visits.prev_special_visit
      ,special_visits.next_special_visit
FROM input
LEFT JOIN special_visits USING(ID, date)

这里的问题是,如果观察(行)本身是特殊访问,我只能观察上一次/下一次特殊访问。我希望像这样的窗口函数中的某种过滤器可能会起作用:

LAG(date) OVER (PARTITION BY ID ORDER BY date WHERE special_visit = 1) AS prev_special_visit 

但不幸的是,它不起作用。您知道如何创建所需的输出吗? 非常感谢!

【问题讨论】:

【参考方案1】:

我不认为 LEAD()LAG() 最适合这个。而是:

SELECT ID, date,
       LAG(date) OVER (PARTITION BY ID ORDER BY date) AS prev_visit,
       LEAD(date) OVER (PARTITION BY ID ORDER BY date) AS next_visit,
       MAX(CASE WHEN special_visit = 1 THEN date END) OVER (PARTITION BY ID ORDER BY DATE ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) as prev_special_visit,
       MIN(CASE WHEN special_visit = 1 THEN date END) OVER (PARTITION BY ID ORDER BY DATE ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) as next_special_visit
FROM input

【讨论】:

嗨,戈登,我在 SQLFiddle 上测试了您的查询:sqlfiddle.com/#!17/404d1/3(我希望此链接有效,您可以看到结果。)但不幸的是,它没有产生预期的结果。 好的,您只需删除订单规范“DESC”,然后它就可以正常工作了。泰!【参考方案2】:

假设日期一直在增加,您可以使用行框进行条件min()s 和max()s,如下所示:

select 
    t.*,
    lag(date) over(partition by id order by date) pre_visit,
    lead(date) over(partition by id order by date) next_visit,
    max(case when special_visit = 1 then date end) over(
        partition by id 
        order by date
        row between unbounded preceding and 1 preceeding
    ) prev_special_visit,
    min(case when special_visit = 1 then date end) over(
        partition by id 
        order by date
        row between 1 following and unbounded following
    ) next_special_visit
from input t

【讨论】:

此查询运行良好。我只需要删除一些错别字: - “row” 到 “rows” - “preceeding” 到 “preceding” 谢谢!

以上是关于如何为不同的观察组使用 LAG 和 LEAD 窗口函数的主要内容,如果未能解决你的问题,请参考以下文章

MYSQL lag() 和lead()函数使用介绍

Hive分析函数LAG和LEAD详解

带有 where 子句的窗口函数(LEAD/LAG)?

在 MonetDB 中最快实现lead() 或 lag() 窗口函数

Hive 分析函数lead、lag实例应用

SQL windows 函数 LEAD/LAG 但只考虑某些值?