堆分析有很好的blog post about lateral joins做一些非常相似的事情。它可能会给你一些想法。你的情况实际上比他们的简单,所以你的解决方案也更容易。
首先几个笔记。您似乎不需要day
输出,因为它总是等于您的输入。其次,无论如何,您每天都需要一个单独的输出列(或者将结果累加到数组中,这看起来不太可取),所以如果您需要可变数量的天数,则必须动态构建SQL那。
为了测试我做了一个表格,并给它几行:
create table messages (user_id integer, created_at timestamp);
insert into messages values (1, now() - interval '5 days'), (1, now() - interval '4 days'), (1, now() - interval '2 days');
insert into messages values (2, now() - interval '10 days'), (2, now() - interval '2 days');
insert into messages values (3, now() - interval '2 days'), (3, now() - interval '1 days');
insert into messages values (4, now() - interval '5 days');
我认为你可以使用横向连接得到一个非常干净的解决方案,有点像上面的文章:
\set start_time '''2016-06-23 06:00:00'''
WITH t(s) AS (
SELECT :start_time::timestamp
)
SELECT COUNT(DISTINCT m1.user_id) AS day_5_messages,
COUNT(DISTINCT m2.user_id) AS day_4_messages,
COUNT(DISTINCT m3.user_id) AS day_3_messages,
COUNT(DISTINCT m4.user_id) AS day_2_messages,
COUNT(DISTINCT m5.user_id) AS day_1_messages
FROM messages m1
CROSS JOIN t
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m1.user_id
AND msub.created_at <@
tsrange(t.s + interval '1 day',
t.s + interval '2 days')
LIMIT 1
) m2
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m2.user_id
AND msub.created_at <@
tsrange(t.s + interval '2 days',
t.s + interval '3 days')
LIMIT 1
) m3
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m3.user_id
AND msub.created_at <@
tsrange(t.s + interval '3 days',
t.s + interval '4 days')
LIMIT 1
) m4
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m4.user_id
AND msub.created_at <@
tsrange(t.s + interval '4 days',
t.s + interval '5 days')
LIMIT 1
) m5
ON true
WHERE m1.created_at <@
tsrange(t.s,
t.s + interval '1 day')
;
这里我使用的是t(s)
CTE只是为了避免重复:start_time
。如果你不喜欢它,它是可选的。当然,在Rails中,您将使用?
而不是:start_time
来对查询进行参数化。
对于测试,将COUNT(...)
替换为array_agg(...)
是有帮助的,因此您可以决定是否包含正确的user_id
。
我认为如果您有created_at
和user_id
(合在一起)的索引,这应该会表现的很好。或者如果你的日子总是在同一时刻开始(比如午夜UTC),那么你可以使用一个功能指数,只有日期(不是时间戳)和user_id
,然后用简单的那一天代替所有的范围条件。这将表现得更好。
还哦:你的查询(和我的)总是只返回一行,这似乎很可疑。我想知道这是否真的是你想要的,或者如果这只是简化你的问题的事情的意外。如果你想每个开始一天一行,那么你可以把你的day
列,按它分组,删除我的WHERE
条件,并根据以前的m
表而不是t.s
做所有的联接。
我可能应该指定,但'user_id'实际上并不是另一个表的关键。这只是一个唯一的字符串标识符。 – mnort9
只是好奇,为什么没有外键呢? –
该字段在我的数据库中实际上并不是'user_id',我只是将其用作此帖的示例。可能是我的一个不好的例子b/c它看起来像一个外键 – mnort9