计算 n 天内不同用户的数量

问题描述 投票:0回答:2

我的表由两个字段组成,

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 纪元而不是时间戳可能会有所帮助,但这只是一个疯狂的猜测。欢迎任何建议。

postgresql postgresql-9.3 window-functions
2个回答
2
投票

应该有效。无法评论速度,但应该比您当前的速度慢很多。希望您在这两个字段上都有索引。

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 秒)。


0
投票

这是一个小的 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
;
© www.soinside.com 2019 - 2024. All rights reserved.