【问题标题】:Conditional aggregation in a window function: gaps and island problem窗口函数中的条件聚合:间隙和孤岛问题
【发布时间】:2021-10-25 09:32:33
【问题描述】:

我正在尝试创建事件的时间线,其中一些是重复的:

源数据

user_id question opinion last_modified
21175381 13 1 2019-03-11
21175381 13 1 2019-03-12
21175381 13 0 2019-03-13
21175381 13 1 2019-03-14
21175381 13 0 2019-03-16
21175381 13 0 2019-03-17
21175381 13 0 2019-03-18

想象在随机的时间段内被问到相同的问题 - 例如“你同意还是不同意一个陈述?”我的工作目标是描述支持的时期——在多长时间内,响应保持为 1 或 0:

目标表

user_id question opinion from until
21175381 13 1 2019-03-11 2019-03-13
21175381 13 0 2019-03-13 2019-03-14
21175381 13 1 2019-03-14 2019-03-16
21175381 13 0 2019-03-16 NULL

最后一个“直到”条目应该是一个NULL,因为这是最后一次测量,我们预计此后不会发生变化。

我尝试了一个带有行偏移的基本窗口函数,但是它没有考虑重复,只是创建了多个具有不正确的开始/截止日期的条目:

连续重复示例 - 不良行为

user_id question opinion from until
21175381 13 1 2019-03-11 2019-03-12
21175381 13 1 2019-03-12 2019-03-13

我在 ADF 中构建它,因此欢迎任何关于如何通过纯 SQL 或 ADF 转换使其工作的想法!非常感谢您的时间、精力和知识!

【问题讨论】:

  • 这看起来不像条件聚合,更像是一个间隙和孤岛问题。
  • 感谢您的评论@Larnu,不知道差距和孤岛问题 - 看起来确实如此,将研究解决方案。
  • 为什么要让直到值包含下一个意见的第一个日期?

标签: sql sql-server group-by azure-data-factory window-functions


【解决方案1】:

我认为解决此问题的最简单方法是使用lag() 在发生变化时保留第一条记录。不需要聚合或joins。

然后lead()得到下一个值:

select user_id, question, opinion,
       last_modified as from,
       lead(last_modified) over (partition by user_id, question order by last_modified) as until
from (select t.*,
             lag(opinion) over (partition by user_id, question order by last_modified) as prev_opinion
      from t
     ) t
where prev_opinion <> opinion or prev_opinion is null;

我认为这是获得所需结果的最简单方法。它也应该有最好的性能。

Here 是一个 dbfiddle。

【讨论】:

  • 优雅的@GordonLinoff。给我点赞。我在几分钟内创建了一个 simple test rig,其中包含 50 万条记录,这些记录在我当地的 2019 年。你的是最快的选择,我的紧随其后。有趣的是,公认的答案是很长一段时间内性能最差的,并且所有查询在临时表上的任何类型的索引下运行速度都较慢。
【解决方案2】:

使用LAG()SUM() 窗口函数创建opinion 的值未更改的组,并按user_id 分组,问题以获取CTE 内每个组的最小日期。
然后进行 CTE 的自联接:

WITH cte AS (
  SELECT user_id, question, grp, opinion, MIN(last_modified) [from]
  FROM (
    SELECT *, SUM(CASE WHEN opinion <> prev_opinion THEN 1 ELSE 0 END) OVER (PARTITION BY user_id, question ORDER BY last_modified) grp
    FROM (
      SELECT *, LAG(opinion, 1, ~opinion) OVER (PARTITION BY user_id, question ORDER BY last_modified) prev_opinion
      FROM tablename
    ) t  
  ) t  
  GROUP BY user_id, question, opinion, grp
)
SELECT c1.user_id, c1.question, c1.opinion, c1.[from], c2.[from] until
FROM cte c1 LEFT JOIN cte c2
ON c2.user_id = c1.user_id AND c2.question = c1.question AND c2.grp = c1.grp + 1

请参阅demo

【讨论】:

  • 非常感谢!有效!我做的一个小修改是第 2 行的 MAX(cast(opinion as int)) - 源是位,而 MIN 不适用于位。
  • @d1sh4 没问题。实际上是不需要的。如果您将其添加到 group by 子句中,您可以只选择意见:dbfiddle.uk/…
  • 不明白为什么需要自加入,就用LEAD
  • @d1sh4 如果意见是 BIT 我做了一个更改:LAG(opinion, 1, ~opinion) 而不是 LAG(opinion, 1, -1)
【解决方案3】:

@forpas's answer 的基础上,您可以使用LEAD 删除自加入

SELECT
  user_id,
  question,
  grp,
  opinion,
  MIN(last_modified) [from],
  LEAD(MIN(last_modified)) OVER
    (PARTITION BY user_id, question ORDER BY grp) [until]
FROM (
    SELECT *, SUM(CASE WHEN opinion <> prev_opinion THEN 1 ELSE 0 END) OVER (PARTITION BY user_id, question ORDER BY last_modified) grp
    FROM (
        SELECT *,
          LAG(opinion, 1, -1) OVER (PARTITION BY user_id, question ORDER BY last_modified) prev_opinion
        FROM tablename
    ) t  
) t  
GROUP BY user_id, question, opinion, grp;

db<>fiddle

【讨论】:

    猜你喜欢
    • 2021-03-07
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    相关资源
    最近更新 更多