我在Postgres 9.3中有以下event
表:
CREATE TABLE event (
event_id integer PRIMARY KEY,
user_id integer,
event_type varchar,
event_time timestamptz
);
我的目标是检索所有user_id
之间的任何事件之间(或最后一次事件与当前时间之间)至少30天的差距。另一个复杂因素是,我只希望具有其中一个差距的用户在执行某个event_type
'convert'
时发生。如何轻松完成?
event
表中的一些示例数据可能如下所示:
INSERT INTO event (event_id, user_id, event_type, event_time)
VALUES
(10, 1, 'signIn', '2015-05-05 00:11'),
(11, 1, 'browse', '2015-05-05 00:12'), -- no 'convert' event
(20, 2, 'signIn', '2015-06-07 02:35'),
(21, 2, 'browse', '2015-06-07 02:35'),
(22, 2, 'convert', '2015-06-07 02:36'), -- only 'convert' event
(23, 2, 'signIn', '2015-08-10 11:00'), -- gap of >= 30 days
(24, 2, 'signIn', '2015-08-11 11:00'),
(30, 3, 'convert', '2015-08-07 02:36'), -- starting with 1st 'convert' event
(31, 3, 'signIn', '2015-08-07 02:36'),
(32, 3, 'convert', '2015-08-08 02:36'),
(33, 3, 'signIn', '2015-08-12 11:00'), -- all gaps below 30 days
(33, 3, 'browse', '2015-08-12 11:00'), -- gap until today (2015-08-20) too small
(40, 4, 'convert', '2015-05-07 02:36'),
(41, 4, 'signIn', '2015-05-12 11:00'); -- gap until today (2015-08-20) >= 30 days
预期结果:
user_id
--------
2
4
答案 0 :(得分:2)
一种方法:
SELECT user_id
FROM (
SELECT user_id
, lead(e.event_time, 1, now()) OVER (PARTITION BY e.user_id ORDER BY e.event_time)
- event_time AS gap
FROM ( -- only users with 'convert' event
SELECT user_id, min(event_time) AS first_time
FROM event
WHERE event_type = 'convert'
GROUP BY 1
) e1
JOIN event e USING (user_id)
WHERE e.event_time >= e1.first_time
) sub
WHERE gap >= interval '30 days'
GROUP BY 1;
如果没有“下一行”,window function lead()
允许包含默认值,这样可以方便地满足您的额外要求“或者在他们的最后一次事件和当前时间之间”。
如果你的桌子很大,你至少应该在(user_id, event_time)
上有一个索引:
CREATE INDEX event_user_time_idx ON event(user_id, event_time);
如果您经常这样做并且event_type
'转换'很少,请添加另一个部分索引:
CREATE INDEX event_user_time_convert_idx ON event(user_id, event_time)
WHERE event_type = 'convert';
并且只有30天的差距普通(不是罕见的情况) 指数变得更加重要 试试这个recursive CTE可以获得更好的效果:
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT DISTINCT ON (user_id)
user_id, event_time, interval '0 days' AS gap
FROM event
WHERE event_type = 'convert'
ORDER BY user_id, event_time
)
UNION ALL
SELECT c.user_id, e.event_time, COALESCE(e.event_time, now()) - c.event_time
FROM cte c
LEFT JOIN LATERAL (
SELECT e.event_time
FROM event e
WHERE e.user_id = c.user_id
AND e.event_time > c.event_time
ORDER BY e.event_time
LIMIT 1 -- the next later event
) e ON true -- add 1 row after last to consider gap till "now"
WHERE c.event_time IS NOT NULL
AND c.gap < interval '30 days'
)
SELECT * FROM cte
WHERE gap >= interval '30 days';
它有更多的开销,但可以停止 - 每个用户 - 在第一个足够大的差距。如果那应该是最后一个事件 now 之间的差距,那么结果中的event_time
为NULL。
新的SQL Fiddle,其中有更多显示测试数据,显示两个查询。
这些相关答案中的详细解释:
答案 1 :(得分:0)
这是另一种方式,可能不像@Erwin那样整洁,但所有步骤都分开,因此很容易适应。
#'
的事件{ begin = '(^[ \t]+)?(?=#'' )';
end = '(?!\G)';
beginCaptures = { 1 = { name = 'punctuation.whitespace.comment.leading.r'; }; };
patterns = (
{ name = 'comment.line.number-sign-tick.r';
begin = "#' ";
end = '\n';
beginCaptures = { 0 = { name = 'punctuation.definition.comment.r'; }; };
},
);
},
第一次出现(在这种情况下只有convert
)user_id
user_id = 2222
因此可以计算日期差异。 user_id
范围,因此您可以查看这是否是您想要的结果。
rnum = rnum + 1