我正在使用 Snowflake,我有一个数据集,其中包含一天内打入的每个电话。其中一些由于 2 分钟内多次尝试而被我们视为无效。
这是一个例子:
用户 | 通话日期 |
---|---|
1 | 2024年1月19日16:24:11 |
1 | 2024年1月19日16:27:29 |
1 | 2024年1月19日16:27:34 |
1 | 2024年1月19日16:27:38 |
1 | 2024年1月19日16:27:43 |
1 | 2024年1月19日16:29:29 |
2 | 2024年1月19日11:08:49 |
2 | 2024年1月19日11:09:32 |
2 | 2024年1月19日11:12:24 |
2 | 2024年1月19日11:14:49 |
期望的结果如下所示:
用户 | 通话日期 |
---|---|
1 | 2024年1月19日16:24:11 |
1 | 2024年1月19日16:27:29 |
1 | 2024年1月19日16:29:29 |
2 | 2024年1月19日11:08:49 |
2 | 2024年1月19日11:12:24 |
2 | 2024年1月19日11:14:49 |
每个用户的首次通话日期将被统计,首次通话日期后2分钟内的任何通话均视为无效通话。
目前我正在使用Python来迭代这个过程,但想知道是否有一种方法可以在雪花中使用来节省时间。
实际上,您可以使用 SQL 查询直接在 Snowflake 中执行此操作,无需在 Python 中进行迭代。这个想法是:
这是 SQL 查询:
WITH RankedCalls AS (
SELECT
user,
"Call Date",
LAG("Call Date") OVER (PARTITION BY user ORDER BY "Call Date") AS prev_call,
CASE
WHEN LAG("Call Date") OVER (PARTITION BY user ORDER BY "Call Date") IS NULL THEN 'valid'
WHEN DATEDIFF('second', LAG("Call Date") OVER (PARTITION BY user ORDER BY "Call Date"), "Call Date") >= 120 THEN 'valid'
ELSE 'invalid'
END AS call_status
FROM your_table_name
),
FilteredCalls AS (
SELECT user, "Call Date"
FROM RankedCalls
WHERE call_status = 'valid'
)
SELECT *
FROM FilteredCalls
ORDER BY user, "Call Date";
根据 2 分钟规则,这只会为您提供有效的呼叫。
另一种可能更便携的可能性是使用递归 CTE。
这里是 PostgreSQL 风格的递归(将在我的 SQLFiddle 中的解决方案 3. 中实现完全工作的示例):
with recursive
-- Index our entries:
i as (select row_number() over (partition by u order by d) id, * from t),
-- Know whose election as a "first" will make each entry redundant for good.
l as
(
-- "pass" increments to keep hesitating entries queued for the next iteration
-- "kind":
-- 1: first of a serie
-- 0: don't know yet if first of serie or not
-- -1: confirmed duplicate (follows a confirmed first)
-- -2: first, but finished (has no more followers to evaluate)
select *, 0 pass, 0 kind from i
union
select id, u, d, pass + 1,
case
when kind = 0 then
case
-- Is the preceding entry more than 2 mn before? We're a first!
when coalesce(lag(d) over byu < d - interval '2 minutes', true) then 1
-- Else (if preceding is less than 2 mn away), if said preceding entry is itself a first, we're a duplicate.
when lag(kind) over byu = 1 then -1
-- Still not sure.
else 0
end
-- If we are a first but have no more followers waiting for us, get away.
when kind = 1 and coalesce(lead(kind) over byu <> 0, true) then -2
else kind
end
from l
where kind >= 0 -- Only work with confirmed firsts, and still hesitating ones.
and pass < 99 -- In case I missed something...
window byu as (partition by u order by d)
)
--select * from l;
select u, d from i where (u, id) in (select u, id from l where kind = -2)
order by u, d;
请注意,它有点复杂且未优化得可怕,因为递归性
not exists (select 1 from accu where alreadyConfirmedAsAFirst)
)
自定义聚合函数,可以在 SQL 中实现预期目标,
(非常直观地)记得上次为参赛作品“授予”“系列第一”徽章的时间,
因此(在以下条目上)可以确定它成为新的“第一”或在“第一”之后仍处于 2 分钟 DMZ 之下。
通过添加的测试用例(小提琴中的解决方案2)来验证边缘情况。
create type firstAndWhen as (first bool, lastFirst timestamp);
create function firstSinceNSecondsAgg(accu firstAndWhen, new timestamp, nSeconds int) returns firstAndWhen language sql as
$$
select
coalesce(new >= accu.lastFirst + cast(nSeconds||' seconds' as interval), true),
case when coalesce(new >= accu.lastFirst + cast(nSeconds||' seconds' as interval), true)
then new
else accu.lastFirst
end
$$;
create function firstSinceNSecondsPop(accu firstAndWhen) returns bool language sql as 'select accu.first';
create aggregate firstSinceNSeconds(timestamp, int)
(
stype = firstAndWhen,
initcond = '(false,)',
sfunc = firstSinceNSecondsAgg,
finalfunc = firstSinceNSecondsPop
);
with l as (select *, firstSinceNSeconds(d, 120) over (partition by u order by d) ok from t)
select u, d from l where ok order by u, d;
优点和缺点: