计算雪花中重叠时间会话的不同分钟

问题描述

我有如下表格:

会话 session_start session_end half_hour_start half_hour_end
A A001 9/13/2020 7:58:00 PM 9/13/2020 晚上 8:10:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00
A A002 9/13/2020 8:02:00 PM 2020/9/13 晚上 8:13:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00
A A003 2020/9/13 晚上 8:27:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00
B B001 9/13/2020 晚上 8:20:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00
B B002 2020/9/13 晚上 8:28:00 2020/9/13 晚上 8:43:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00

目标是计算每个人在 30 分钟块(half_hour_start - half_hour_end)内的所有会话的不同分钟数。计数从 00 分钟开始,到 29 分钟结束(因此总共有 30 个不同的分钟)。

这样,即使某人的会话从 2020 年 9 月 13 日晚上 8:00:01 开始到 2020 年 9 月 13 日晚上 8:00:05 结束,此人仍将获得 1 分钟的积分- 分钟'00'。我们感兴趣的不是完整分钟数的计数,而是会话发生的所有不同分钟数的计数,即使是部分时间。

我需要得到如下结果:

---旧版本---

distinct_minutes_count
A 14
B 10

---新版本---

distinct_minutes_count
A 16
B 10

(可能来自:

会话 session_start session_end half_hour_start half_hour_end distinct_minutes_count_per_person
A A001 9/13/2020 7:58:00 PM 9/13/2020 晚上 8:10:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 16
A A002 9/13/2020 8:02:00 PM 2020/9/13 晚上 8:13:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 16
A A003 2020/9/13 晚上 8:27:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 16
B B001 9/13/2020 晚上 8:20:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 10
B B002 2020/9/13 晚上 8:28:00 2020/9/13 晚上 8:43:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 10

)

所需的中间步骤可能是:

会话 session_start session_end half_hour_start half_hour_end distinct_minute_per_session distinct_minutes_count_per_session distinct_minute_per_person distinct_minutes_count
A A001 9/13/2020 7:58:00 PM 9/13/2020 晚上 8:10:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 00,01,02,03,04,05,06,07,08,09,10 11 00,10,11,12,13,27,28 16
A A002 9/13/2020 8:02:00 PM 2020/9/13 晚上 8:13:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 02,13 12 00,28 16
A A003 2020/9/13 晚上 8:27:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 27,28 2 00,28 16
B B001 9/13/2020 晚上 8:20:00 2020/9/13 晚上 8:28:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 20,21,22,23,24,25,26,28 9 20,28,29 10
B B002 2020/9/13 晚上 8:28:00 2020/9/13 晚上 8:43:00 9/13/2020 晚上 8:00:00 2020/9/13 晚上 8:30:00 28,29 2 20,29 10

但我没有看到为 SNowflake 中的列创建列表值的选项。

解决方法

以下内容应该可以帮助您完成大部分工作。您可能会在 CTE 中删除很多列 - 我只是将它们放在那里以检查中间步骤。 可能还有很多其他场景不在您的示例数据中,所以我没有检查,但您可能应该例如如果会话 session_start/session_end 完全在另一个会话的持续时间内

WITH TEMP1 AS
    (
        SELECT
            PERSON,SESSION,SESSION_START,SESSION_END,HALF_HOUR_START,HALF_HOUR_END,GREATEST(SESSION_START,HALF_HOUR_START)                                                     CALC_START,LEAST(SESSION_END,HALF_HOUR_END)                                                            CALC_END,DATEDIFF(MINUTE,HALF_HOUR_START),HALF_HOUR_END)) DUR -- Adjust to allow for partial minutes if necessary,by adding 1 to this value,RANK() OVER (PARTITION BY PERSON,HALF_HOUR_START ORDER BY SESSION_START ASC) RNK
        FROM
            SESSION_DATA
    ),TEMP2 AS
    (
        SELECT
            T1.PERSON,T1.SESSION,T1.DUR,T1.RNK,T1.CALC_START,T1.CALC_END,CASE
                WHEN T2.PERSON IS NULL -- Must be first session in the half-hour slot for the person
                    THEN T1.DUR
                    ELSE DATEDIFF(MINUTE,T2.CALC_END,T1.CALC_END)
            END DISTINCT_DUR 
        FROM
            TEMP1 T1
            LEFT OUTER JOIN -- join to previous session within the same half-hour slot for the same user
                TEMP1 T2
                ON
                    T1.PERSON              = T2.PERSON
                    AND T1.HALF_HOUR_START = T2.HALF_HOUR_START
                    AND T1.RNK             = (T2.RNK + 1)
    )
SELECT
    PERSON,SUM(DISTINCT_DUR)
FROM
    TEMP2
GROUP BY
    PERSON
;

评论后编辑

正如我所说,这是一个可以帮助您的起点,我还没有使用会话数据的所有可能组合对此进行测试。如果您对 CASE 语句进行以下更改,它会在您包含会话 A003 时起作用:

,CASE 
  WHEN T2.PERSON IS NULL THEN T1.DUR
  WHEN T1.CALC_START > T2.CALC_END THEN T1.DUR
 ELSE DATEDIFF(MINUTE,T1.CALC_END) END DISTINCT_DUR
,

要生成多行,可以使用一个JS表UDF:

CREATE OR REPLACE FUNCTION generate_minutes(STARTING timestamp,ENDING timestamp)
RETURNS TABLE (V VARCHAR)
LANGUAGE JAVASCRIPT
AS '{
    processRow: function get_params(row,rowWriter,context){
       for(var i=row.STARTING/60/1000; i<=row.ENDING/60/1000; i++) {
           rowWriter.writeRow({V: i}); 
       }
    }
}';


with data as (
select 'A' person,'A001' session,'9/13/2020 7:58:00'::timestamp session_start,'9/13/2020 8:10:00'::timestamp session_end,'9/13/2020 8:00:00'::timestamp half_hour_start,'9/13/2020 8:30:00'::timestamp half_hour_end 
union all select 'A','A002','9/13/2020 8:02:00','9/13/2020 8:13:00','9/13/2020 8:00:00','9/13/2020 8:30:00'
union all select 'A','A003','9/13/2020 8:27:00','9/13/2020 8:28:00','9/13/2020 8:30:00'
)

select person,count(distinct x.v) distinct_minutes
from data,table(generate_minutes(
      greatest(session_start,half_hour_start),least(session_end,timestampadd(minute,-1,half_hour_end)))
     ) x
where session_start<=half_hour_end and session_end >= half_hour_start
group by person

enter image description here

,

所以使用这两个兴趣周期表和会话表分开开始

with periods as (
    select p_from::timestamp_ntz as p_from,p_to::timestamp_ntz as p_to
    from values 
    ('2020-09-13 20:00:00','2020-09-13 20:30:00' ),('2020-09-13 20:30:00','2020-09-13 21:00:00' )
    v(p_from,p_to)
),sessions as (
    select user,session,s_from::timestamp_ntz as s_from,s_to::timestamp_ntz as s_to
    from values 
    ('A','A001','2020-09-13 19:58:00','2020-09-13 20:10:00'),('A','2020-09-13 20:02:00','2020-09-13 20:13:00'),'2020-09-13 20:27:00','2020-09-13 20:28:00'),('B','B001','2020-09-13 20:20:00','B002','2020-09-13 20:28:00','2020-09-13 20:43:00')
    v(user,s_from,s_to)

然后我们首先将它们混合在一起以重新创建您所拥有的 p_s_mix,然后将会话时间修剪到时间段的边界(也就是使用 t_fromt_to 完成)然后我们可以得到那些的日期分钟部分(又名 from_min & `to_min)

),p_s_mix as (
    select s.*,p.*
    from sessions as s
    join periods as p
    where s.s_from < p.p_to and s.s_to > p.p_from
    order by 1,2
),p_s_trim AS (
    select user,session
        --,s_from
        --,s_to,p_from,p_to,greatest(p_from,s_from) as t_from,least(p_to,s_to) as t_to,date_part('minute',t_from) as from_min,t_to) as to_min
    from p_s_mix

然后我们可以将其与预编码的 60 分钟时段的总表混合,并计算不同的分钟数。只是为了好玩,提供了 array_agg 以显示示例中给出的形式的数组。

),gross AS (
    SELECT * from values
    (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39),(40),(41),(42),(43),(44),(45),(46),(47),(48),(49),(50),(51),(52),(53),(54),(55),(56),(57),(58),(59)
    v(slot)
)
select p.user,p.p_from,p.p_to,count(distinct g.slot) as c_mintues,array_agg(distinct g.slot) within group(order by g.slot) as distinct_mintues
 from p_s_trim as p
 join gross as g
    on p.from_min <= g.slot and p.to_min >= g.slot
 group by 1,2,3
 order by 1,3
;

给予:

USER  P_FROM                   P_TO                     C_MINTUES  DISTINCT_MINTUES 
A     2020-09-13 20:00:00.000  2020-09-13 20:30:00.000  16         [ 0,1,3,4,5,6,7,8,9,10,11,12,13,27,28]
B     2020-09-13 20:00:00.000  2020-09-13 20:30:00.000  11         [ 20,21,22,23,24,25,26,28,29,30]
B     2020-09-13 20:30:00.000  2020-09-13 21:00:00.000  14         [ 30,31,32,33,34,35,36,37,38,39,40,41,42,43]

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...