如何转换此mysql查询以在snowflake / mpp上运行

时间:2018-05-18 00:52:35

标签: sql snowflake-datawarehouse

背景

我正在努力将这个mysql查询转换为一个数据库,该数据库没有可以在行级别设置的变量,就像使用mysql一样。我不确定没有循环就可以做到这一点,但这就是目标。

问题

我们有一个客户ID id和一个会话时间戳event_datetime

对于每个客户,我需要根据以下定义将每个会话解释为有效或无效:

  • 有效会话在30分钟后到期。
  • 如果会话在先前有效会话的时间范围内未发生,则会话有效。

替代定义很容易计算:如果会话自上次会话起已经过了30分钟,则会话有效。但那不是我在这里的目标。

例如:

2018-01-01 00:00:00 <-- valid
2018-01-01 00:15:00 <-- invalid
2018-01-01 00:31:00 <-- valid
2018-01-01 01:14:00 <-- valid
2018-01-01 01:17:00 <-- invalid
2018-01-01 01:25:00 <-- invalid
2018-01-01 01:43:00 <-- invalid
2018-01-01 01:45:00 <-- valid

我只是想避免循环。使用通常可用的任何分析/窗口函数都可以。最终我试图在雪花上实现这个。

我尝试了什么

我试图用窗口函数,连接,不存在来提出一些东西,但努力寻找解决方案。例如,做一个会话时间差异的运行总和看起来很有希望,但我想不出如何在达到30分钟后将累积和重置为零。我知道我可以订购每个客户的会话并循环(以便最大迭代次数将是单个客户的最大会话数),但试图避免这种情况。

示例数据和mysql解决方案

以下是使用mysql的解决方案。计算两个定义(30分钟失效和30分钟到期)。

DROP TABLE IF EXISTS work.test;
CREATE TABLE work.test (id INT, event_datetime DATETIME);
INSERT INTO work.test
VALUES (123456789, '2017-12-08 15:24:29.297000000'),
    (123456789, '2017-12-08 15:25:42.510000000'),
    (123456789, '2017-12-08 15:28:49.023000000'),
    (123456789, '2017-12-10 07:23:49.693000000'),
    (123456789, '2017-12-10 07:25:03.487000000'),
    (123456789, '2017-12-10 07:35:52.613000000'),
    (123456789, '2017-12-10 07:45:52.613000000'),
    (123456789, '2017-12-10 07:55:52.613000000'),
    (123456789, '2017-12-10 08:05:52.613000000'),
    (123456789, '2017-12-10 15:55:24.070000000'),
    (123456789, '2017-12-10 15:55:57.063000000'),
    (123456789, '2017-12-10 15:56:37.633000000'),
    (123456789, '2017-12-17 09:00:41.543000000'),
    (123456789, '2017-12-17 09:02:13.187000000'),
    (123456789, '2017-12-17 09:02:47.370000000'),
    (123456789, '2017-12-17 09:03:29.843000000'),
    (123456789, '2017-12-17 09:03:56.667000000'),
    (123456789, '2017-12-17 09:06:12.493000000'),
    (123456789, '2017-12-17 09:07:26.113000000');


SELECT
    @last_session_datetime AS last_session_datetime,
    @diff := timestampdiff(MINUTE, @last_session_datetime, s.event_datetime) AS diff,
    if(@diff IS NULL OR @diff >= 30, 'valid', 'not valid') AS valid_30_minute_lapse,
    @last_visit_datetime := if(@curr_customer_id = s.id AND timestampdiff(MINUTE, @last_visit_datetime, s.event_datetime) < 30, @last_visit_datetime, s.event_datetime) AS last_visit_datetime,
    if(@last_visit_datetime = s.event_datetime, 'valid', 'not valid') AS valid_30_minute_expiration,
    @curr_customer_id := s.id,
    id,
    event_datetime,
    @last_session_datetime := s.event_datetime
FROM work.test s
JOIN (
         SELECT
             @curr_customer_id := 0,
             @last_visit_datetime := '1900-01-01',
             @last_session_datetime := NULL) a
ORDER BY s.id, s.event_datetime

在此示例数据中,会话2017-12-10 07:55:53根据30分钟到期有效,但无效根据30分钟失效。在上一次会议之后仅10分钟,但距离上次验证会议超过30分钟。

2 个答案:

答案 0 :(得分:2)

Snowflake的表值Javascript UDF功能非常适合此类查询。基本上,您可以定义自己的窗口函数,按顺序跟踪日期流,并输出&#34;有效&#34;与&#34;无效&#34;基于先前&#34;有效&#34;的值看到的价值。 (Javascript表值UDF记录在案here)。

下面是一个代码示例:

CREATE OR REPLACE FUNCTION classify (ts string)
RETURNS table (valid string)
LANGUAGE JAVASCRIPT
STRICT
IMMUTABLE
AS '
{


 initialize: function (argumentInfo, context) {
    validStart = Date.parse("0000-00-00");
 },


 processRow: function (row, rowWriter, context) {
    var thisDate = Date.parse(row.TS);
    var minsDiff = (thisDate - validStart) / (1000 * 60);
    if (minsDiff < 30) {
       rowWriter.writeRow({VALID:  "invalid"});
    }
    else {
       validStart = thisDate;
       rowWriter.writeRow({VALID:  "valid"})
    }

 },


 finalize: function (rowWriter, context) {/*...*/},

 }
 ';

现在,您可以在流中的每一行上调用此函数,如下所示...(可能您希望&#34;有效/无效&#34;根据ID分组计算分段值):

select * from test,  
table(classify(event_datetime::string) 
      over (partition by id order by event_datetime));

当您对样本数据进行运行时,结果如下:

enter image description here

答案 1 :(得分:0)

基本思想似乎是由 allsubsets.addAll(moresubsets); 子查询捕获的:

not exists