我的表由两个字段组成,
CalDay
一个时间戳字段,时间设置为 00:00:00 和UserID
。
它们一起形成一个复合键,但重要的是要记住,每个给定的日历日都有很多行,并且给定的一天没有固定的行数。
根据这个数据集,我需要计算在设定的时间窗口(例如 30 天)内有多少个不同的用户。
使用 postgres 9.3,我无法使用
COUNT(Distinct UserID) OVER ...
,也无法使用 DENSE_RANK() OVER (... RANGE BETWEEN)
解决该问题,因为 RANGE
只接受 UNBOUNDED
。
所以我采用了老式的方式并尝试使用标量子查询:
SELECT
xx.*
,(
SELECT COUNT(DISTINCT UserID)
FROM data_table AS yy
WHERE yy.CalDay BETWEEN xx.CalDay - interval '30 days' AND xx.u_ts
) as rolling_count
FROM data_table AS xx
ORDER BY yy.CalDay
理论上来说,这应该可行,对吧?我还不确定,因为我大约 20 分钟前开始查询,但它仍在运行。问题就在这里,数据集仍然相对较小(25000 行),但会随着时间的推移而增长。我需要一些可扩展且性能更好的东西。
我在想也许——只是也许——使用 unix 纪元而不是时间戳可能会有所帮助,但这只是一个疯狂的猜测。欢迎任何建议。
这应该有效。无法评论速度,但应该比您当前的速度慢很多。希望您在这两个字段上都有索引。
SELECT t1.calday, COUNT(DISTINCT t1.userid) AS daily, COUNT(DISTINCT t2.userid) AS last_30_days
FROM data_table t1
JOIN data_table t2
ON t2.calday BETWEEN t1.calday - '30 days'::INTERVAL AND t1.calday
GROUP BY t1.calday
更新
用大量数据进行测试。上面的方法有效,但速度很慢。这样做要快得多:
SELECT t1.*, COUNT(DISTINCT t2.userid) AS last_30_days
FROM (
SELECT calday, COUNT(DISTINCT userid) AS daily
FROM data_table
GROUP BY calday
) t1
JOIN data_table t2
ON t2.calday BETWEEN t1.calday - '30 days'::INTERVAL AND t1.calday
GROUP BY 1, 2
因此,它不是为所有 JOIN 组合构建一个庞大的表,然后进行分组/聚合,而是首先获取“每日”数据,然后加入 30 天的数据。保持连接更小并快速返回(在我的系统上源表中的 45000 行仅不到 1 秒)。
这是一个小的 postgres 示例。它带来了虚拟计数变量,但您可以稍后将其删除。不是性能方面的专家,但我认为它会正常工作。 LMK 如果你有更好的主意。
drop table if exists userdays;
create table userdays
(
activedate date,
uid int
)
;
insert into userdays
values
('9-23-2023',1),
('9-23-2023',3),
('9-24-2023',1),
('9-24-2023',2),
('9-24-2023',3),
('9-25-2023',1),
('9-25-2023',2),
('9-25-2023',3),
('9-25-2023',4),
('9-26-2023',1),
('9-26-2023',2),
('9-26-2023',3),
('9-26-2023',4),
('9-27-2023',1),
('9-27-2023',2),
('9-27-2023',3),
('9-27-2023',4)
;
select
activity_date
,ucount
,sum(ucount) uad
,sum(ucount) over (order by t1.activity_date rows between 3 preceding and current row) au3d
from
(
select
to_date(t1.activedate::text,'YYYY-MM-DD') activity_date
,t1.uid
,1 ucount
,count(*)
from userdays t1
group by 1,2,3
) t1
group by 1,ucount
order by 1 desc,ucount
;