问题描述
我有如下表格:
人 | 会话 | 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
,
所以使用这两个兴趣周期表和会话表分开开始
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_from
和 t_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]