如何在 snowflake / mpp 上将此 mysql 查询转换为 运行
How to convert this mysql query to run on snowflake / mpp
背景
我正在努力将此 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
我只是想避免循环。使用任何常用的分析/window 函数都可以。最终我试图在雪花上实现它。
我试过的
我尝试使用 window 函数、连接、不存在来想出一些办法,但正在努力寻找解决方案。例如,对会话时间差异进行 运行 求和似乎很有希望,但我想不出如何在达到 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 分钟。
not exists
子查询似乎包含基本思想:
select t.*
from work.test t
where not exists (select 1
from work.test t2
where t2.id = t.id and
t2.event_datetime > t.event_datetime - interval '30 minute' nd
t2.event_datetime < t.event_datetime
);
Snowflake 的 table 值 Javascript UDF 功能非常适合此类查询。本质上,您可以定义自己的窗口函数来按顺序跟踪日期流,并根据之前看到的 "valid" 值输出 "valid" 与 "invalid" 值。 (Javascript Table 记录了有价值的 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) {/*...*/},
}
';
现在您可以按如下方式在流中的每一行上调用此函数...(大概您希望根据 ID 分组计算 "valid/invalid" 值):
select * from test,
table(classify(event_datetime::string)
over (partition by id order by event_datetime));
当 运行 遍历您的示例数据时,结果如下:
背景
我正在努力将此 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
我只是想避免循环。使用任何常用的分析/window 函数都可以。最终我试图在雪花上实现它。
我试过的
我尝试使用 window 函数、连接、不存在来想出一些办法,但正在努力寻找解决方案。例如,对会话时间差异进行 运行 求和似乎很有希望,但我想不出如何在达到 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 分钟。
not exists
子查询似乎包含基本思想:
select t.*
from work.test t
where not exists (select 1
from work.test t2
where t2.id = t.id and
t2.event_datetime > t.event_datetime - interval '30 minute' nd
t2.event_datetime < t.event_datetime
);
Snowflake 的 table 值 Javascript UDF 功能非常适合此类查询。本质上,您可以定义自己的窗口函数来按顺序跟踪日期流,并根据之前看到的 "valid" 值输出 "valid" 与 "invalid" 值。 (Javascript Table 记录了有价值的 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) {/*...*/},
}
';
现在您可以按如下方式在流中的每一行上调用此函数...(大概您希望根据 ID 分组计算 "valid/invalid" 值):
select * from test,
table(classify(event_datetime::string)
over (partition by id order by event_datetime));
当 运行 遍历您的示例数据时,结果如下: