【问题标题】:Group a series of events into one line with a start and end time将一系列事件分组到一行中,并带有开始和结束时间
【发布时间】:2021-10-16 07:03:25
【问题描述】:

这个问题是关于 SQL(特别是使用 Presto)。

我遇到了代理与任务交互(评论、获取所有权、添加标签等)的情况,我试图将每一系列交互分组到一行中,并汇总开始和结束时间。

基本上,表格目前看起来像这样:

Task Name Agent Name Event Type Time
Computer Repair Help Dave Claim 2:35PM
Computer Repair Help Dave Comment 2:37PM
Computer Repair Help Dave Tag 2:39PM
Computer Repair Help Dave Pass 2:45PM
Computer Repair Help Susie Claim 2:56PM
Computer Repair Help Susie Tag 2:58PM
Computer Repair Help Susie Pass 3:02PM
Computer Repair Help Dave Claim 3:05PM
Computer Repair Help Dave Comment 3:08PM
Computer Repair Help Dave Comment 3:11PM
Computer Repair Help Dave Close 3:16PM

但我希望得到这样的东西:

Task Name Agent Name Start Time End Time
Computer Repair Help Dave 2:35PM 2:45PM
Computer Repair Help Susie 2:56PM 3:02PM
Computer Repair Help Dave 3:05PM 3:16PM

我尝试按任务名称和代理名称进行分区以获得每次交互的第一行和最后一行,但主要问题是,如果代理有两个交互窗口,它会给我所有交互的总范围而不是像示例中那样分开。不确定如何告诉查询在每次代理名称更改后将其视为新交互。

任何帮助都将不胜感激......几周以来我一直在为此苦苦挣扎,似乎必须有一些我没有看到的简单解决方案。谢谢!

【问题讨论】:

  • 开始和结束的定义是什么?
  • 他们在系列中的第一个事件的时间和他们在系列中的最后一个事件的时间

标签: sql presto gaps-and-islands trino


【解决方案1】:

您可以使用MATCH_RECOGNIZE 解决此问题,Trino (formerly known as Presto SQL) 的最新版本中提供了此功能:

WITH data(task, agent, event, ts) AS (
    VALUES ('Computer Repair Help', 'Dave', 'Claim', '2:35PM'),
           ('Computer Repair Help', 'Dave', 'Comment', '2:37PM'),
           ('Computer Repair Help', 'Dave', 'Tag', '2:39PM'),
           ('Computer Repair Help', 'Dave', 'Pass', '2:45PM'),
           ('Computer Repair Help', 'Susie', 'Claim', '2:56PM'),
           ('Computer Repair Help', 'Susie', 'Tag', '2:58PM'),
           ('Computer Repair Help', 'Susie', 'Pass', '3:02PM'),
           ('Computer Repair Help', 'Dave', 'Claim', '3:05PM'),
           ('Computer Repair Help', 'Dave', 'Comment', '3:08PM'),
           ('Computer Repair Help', 'Dave', 'Comment', '3:11PM'),
           ('Computer Repair Help', 'Dave', 'Close', '3:16PM')
)
SELECT task, agent, start_time, end_time
FROM data MATCH_RECOGNIZE (
        PARTITION BY task
        ORDER BY ts
        MEASURES
            FIRST(M.agent) AS agent,
            FIRST(M.ts) AS start_time,
            LAST(M.ts) AS end_time
        ONE ROW PER MATCH
        AFTER MATCH SKIP PAST LAST ROW
        PATTERN (M+)
        DEFINE M AS agent = FIRST(agent)
    )

=>

         task         | agent | start_time | end_time
----------------------+-------+------------+----------
 Computer Repair Help | Dave  | 2:35PM     | 2:45PM
 Computer Repair Help | Susie | 2:56PM     | 3:02PM
 Computer Repair Help | Dave  | 3:05PM     | 3:16PM
(3 rows)

基本思路是:

  • 按任务对数据集进行分区,按时间排序
  • 匹配代理等于序列中第一行代理的行序列
  • 提取序列中第一个和最后一个事件的时间

您可以在此处找到有关 MATCH_RECOGNIZE 的更多详细信息:

【讨论】:

  • 只需阅读 trino 的故事。感谢您对开源的贡献。我会仔细看看这个项目。如果不是现在,我希望以后能够增加一些价值。
  • 该死,这个功能看起来很有用,但我使用的是 AWS Athena,它似乎不允许它,也许我们不是在最新的引擎上。我对这一切还太陌生,不知道为什么,但非常感谢你的回答,让我意识到这一点,期待有一天能够实施!
【解决方案2】:

这可以作为“差距和孤岛”问题来解决。根据您的数据集,我假设每组新的交互都以 Claim 开头。

因此,CTE(也可以重写为子查询)使用SUM 窗口函数在案例表达式的帮助下创建组。最终投影使用此GroupNum(其中RANGE UNBOUNDED PRECEDING 是可选的)、任务和事件类型对数据进行分组,以便为​​每个组找到StartTime 作为MIN(Time)EndTime 作为MAX(Time)

您可以尝试以下方法:

WITH CTE AS (
    SELECT
        *,
        SUM(
            CASE
                WHEN EventType='Claim' THEN 1
                ELSE 0
            END
        ) OVER (
            PARTITION BY TaskName, AgentName 
            ORDER BY Time 
            RANGE UNBOUNDED PRECEDING
        ) as GroupNum
    FROM
        my_table
)
SELECT
    TaskName,
    AgentName,
    MIN(Time) as StartTime,
    MAX(Time) as EndTime
FROM
    CTE
GROUP BY
    TaskName,
    AgentName,
    GroupNum
TaskName AgentName StartTime EndTime
Computer Repair Help Dave 2:35PM 2:45PM
Computer Repair Help Susie 2:56PM 3:02PM
Computer Repair Help Dave 3:05PM 3:16PM

View on DB Fiddle

让我知道这是否适合你。

【讨论】:

  • 刚刚测试了它,它对我有用,可能只是用这个逻辑替换了 100 行荒谬的低效代码(行号上有很多相同的表连接 - 1 等)哈哈,谢谢非常有帮助!
  • 如果没有像“claim”这样的标识符或 3 个标识符,如 walk、run、idle,该怎么办?
  • @AliDemirci 您能否使用可重现的样本数据集和预期结果创建另一个问题。我可以在那里更好地理解和回应。
【解决方案3】:

这是间隙和孤岛问题的变体。使用lag(),您可以找到值变化的位置,然后进行累积求和以分配组。

WITH dataset AS (
    SELECT * FROM (VALUES   
('Computer Repair Help' ,'Dave','   Claim'  ,'2:35PM'),
('Computer Repair Help' ,'Dave','   Comment'    ,'2:37PM'),
('Computer Repair Help' ,'Dave','   Tag',   '2:39PM'),
('Computer Repair Help' ,'Dave','   Pass'   ,'2:45PM'),
('Computer Repair Help' ,'Susie','  Claim'  ,'2:56PM'),
('Computer Repair Help' ,'Susie','  Tag','2:58PM'),
('Computer Repair Help' ,'Susie','  Pass'   ,'3:02PM'),
('Computer Repair Help' ,'Dave  ','Claim'   ,'3:05PM'),
('Computer Repair Help' ,'Dave  ','Comment' ,'3:08PM'),
('Computer Repair Help' ,'Dave  ','Comment' ,'3:11PM'),
('Computer Repair Help' ,'Dave  ','Close'   ,'3:16PM')
                   
 ) AS t (task, name, event_type, time))


select task, name, min(time) as start_time, max(time) as end_time
from (
         select *,
                sum(case when name = prev_id then 0 else 1 end) over (partition by task, name order by time) as grp
         from (
                  select task, name, time,
                         lag(name) over (order by time) as prev_id
                  from dataset
              )
     )
group by task, name, grp

输出:

task name start_time end_time
Computer Repair Help Dave 2:35PM 2:45PM
Computer Repair Help Susie 2:56PM 3:02PM
Computer Repair Help Dave 3:05PM 3:16PM

请注意,您可能需要将时间解析为实际时间。

【讨论】:

  • 差距和岛屿,我知道这种情况必须有一个名字,但不知道用谷歌搜索什么哈哈......一个古老的难题。这个滞后功能似乎很有用,迫不及待地想尝试实施您的建议。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 2017-04-13
  • 2020-03-09
  • 2018-12-17
  • 2016-02-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多