计算 n 天内的不同用户数

Count distinct users over n-days

我的 table 包含两个字段,CalDay 时间戳字段,时间设置在 00:00:00 和 UserID。 它们一起形成一个复合键,但重要的是要记住,每个给定的日历日都有很多行,并且给定的一天没有固定的行数。

基于这个数据集,我需要计算在一组 window 的时间内有多少不同的用户,比如说 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 组合构建大量 table 然后 grouping/aggregating,而是首先获取 "daily" 数据,然后加入 30 天。使连接更小并且 returns 快速(在我的系统上源 table 中的 45000 行不到 1 秒)。