所以我需要使用PostgreSQL,并在COUNT(DISTINCT userid)
中以百分比形式询问每天至7天的变化。
这甚至可能吗?
白天获得独特用户非常简单:
SELECT COUNT(DISTINCT userid), timestamp::date
FROM logs
GROUP BY timestamp::date
ORDER BY timestamp::date DESC
如何将其转换为今天至7天的百分比?
答案 0 :(得分:3)
因此我们需要为第X天取一个值,在第X-7天取第二个值,然后计算%。
查询可能如下所示:
SELECT a.timestamp,
a.cnt,
b.cnt cnt_minus_7_day,
round( 100.0 *( a.cnt - b.cnt ) / b.cnt , 2 ) change_7_days
from (
SELECT timestamp::date, COUNT(DISTINCT userid) cnt
FROM logs
GROUP BY timestamp::date
ORDER BY timestamp::date
) a
left join (
SELECT timestamp::date, COUNT(DISTINCT userid) cnt
FROM logs
GROUP BY timestamp::date
ORDER BY timestamp::date
) b
ON a.timestamp = b.timestamp - 7
;
你也可以尝试另一个版本 - 这个版本应该更快,因为似乎postgresql不是聪明的并且评估同一个子查询两次,
而不是在记忆或临时表中兑现结果
WITH子句有助于避免这种情况(比较下面的计划)。
with src as (
SELECT timestamp::date, COUNT(DISTINCT userid) cnt
FROM logs
GROUP BY timestamp::date
ORDER BY timestamp::date
)
SELECT a.timestamp,
a.cnt,
b.cnt cnt_minus_7_day,
round( 100.0 *( a.cnt - b.cnt ) / b.cnt , 2 ) change_7_days
FROM src a
left join src b
on a.timestamp = b.timestamp - 7
这是第一个查询的计划(运行我的样本数据):
"Hash Left Join (cost=5136.71..5350.93 rows=101 width=20) (actual time=77.778..88.676 rows=101 loops=1)"
" Hash Cond: (public.logs."timestamp" = (b."timestamp" - 7))"
" -> GroupAggregate (cost=2462.13..2672.31 rows=101 width=8) (actual time=44.398..55.129 rows=101 loops=1)"
" -> Sort (cost=2462.13..2531.85 rows=27889 width=8) (actual time=44.290..48.392 rows=27889 loops=1)"
" Sort Key: public.logs."timestamp""
" Sort Method: external merge Disk: 488kB"
" -> Seq Scan on logs (cost=0.00..402.89 rows=27889 width=8) (actual time=0.037..10.396 rows=27889 loops=1)"
" -> Hash (cost=2673.32..2673.32 rows=101 width=12) (actual time=33.355..33.355 rows=101 loops=1)"
" Buckets: 1024 Batches: 1 Memory Usage: 5kB"
" -> Subquery Scan on b (cost=2462.13..2673.32 rows=101 width=12) (actual time=22.883..33.306 rows=101 loops=1)"
" -> GroupAggregate (cost=2462.13..2672.31 rows=101 width=8) (actual time=22.881..33.288 rows=101 loops=1)"
" -> Sort (cost=2462.13..2531.85 rows=27889 width=8) (actual time=22.817..26.507 rows=27889 loops=1)"
" Sort Key: public.logs."timestamp""
" Sort Method: external merge Disk: 488kB"
" -> Seq Scan on logs (cost=0.00..402.89 rows=27889 width=8) (actual time=0.014..3.696 rows=27889 loops=1)"
"Total runtime: 100.360 ms"
和第二版:
"Hash Left Join (cost=2675.59..2680.64 rows=101 width=20) (actual time=60.612..60.785 rows=101 loops=1)"
" Hash Cond: (a."timestamp" = (b."timestamp" - 7))"
" CTE src"
" -> GroupAggregate (cost=2462.13..2672.31 rows=101 width=8) (actual time=46.498..60.425 rows=101 loops=1)"
" -> Sort (cost=2462.13..2531.85 rows=27889 width=8) (actual time=46.382..51.113 rows=27889 loops=1)"
" Sort Key: logs."timestamp""
" Sort Method: external merge Disk: 488kB"
" -> Seq Scan on logs (cost=0.00..402.89 rows=27889 width=8) (actual time=0.037..8.945 rows=27889 loops=1)"
" -> CTE Scan on src a (cost=0.00..2.02 rows=101 width=12) (actual time=46.504..46.518 rows=101 loops=1)"
" -> Hash (cost=2.02..2.02 rows=101 width=12) (actual time=14.084..14.084 rows=101 loops=1)"
" Buckets: 1024 Batches: 1 Memory Usage: 5kB"
" -> CTE Scan on src b (cost=0.00..2.02 rows=101 width=12) (actual time=0.002..14.033 rows=101 loops=1)"
"Total runtime: 67.799 ms"
答案 1 :(得分:3)
您实际上不需要子查询或CTE。 可以使用窗口函数lag()
对单个SELECT
执行操作:
我使用ts
作为列名而不是timestmap
,因为使用reserved words (SQL standard) or Postgres function / type names作为标识符是不明智的。
SELECT ts::date
, ((count(DISTINCT userid) * 10000)
/ lag(count(DISTINCT userid), 7) OVER (ORDER BY ts::date))::real
/ 100 - 100 AS pct_change_since_7_days_ago
,count(DISTINCT userid) AS ct
,lag(count(DISTINCT userid), 7) OVER (ORDER BY ts::date) AS ct_7_days_ago
FROM logs
GROUP BY 1
ORDER BY 1 DESC;
我安排了性能百分比的计算。这样,我们得到2个小数位的舍入精度,而不使用函数round()
which would also require a cast to numeric
。
窗口函数可以应用于同一查询级别的聚合函数,这就是lag(count(DISTINCT userid), 7) OVER (ORDER BY ts::date)
工作的原因。
窗口函数lead()
和lag()
采用其他参数。我选择第7行
注意:这需要每天至少一行,否则会计算错误。如果可能存在差距,second query of @kordirko将是我的选择,只有CTE中没有ORDER BY
,这应该应用于外部查询。
或者,您可以使用generate_series()
和LEFT JOIN
创建一个天数列表。就像在这里证明:
Retrieving row count and returning 0 when no rows
如果“7天前”没有行,则结果为NULL - 对于@ kordirko版本中的LEFT JOIN以及lag()
- 这表示现实很好(“计数未知”)并作为除以0 的自动保护。
但是,如果 userid
可以NULL
,则除以0 成为可能,我们需要了解案例。为什么悖论会产生影响?
与其他聚合函数不同,count()
永远不会返回NULL
。相反,NULL
值不计算在内。
但如果在“7天前”找到没有行,我们会得到NULL
的计数,因为整个表达式为NULL
:{{ 1}}甚至没有被执行 - 在这种情况下,这恰好对我们有用。
但是,如果找到一行或多行 ,但count()
,我们会计算 userid IS NULL
,会将除以0的异常提高。
对于普通0
,我们可以使用count(userid)
来阻止此情况。但这对于count(*)
来说是不可能的 - 并且可能会或可能不会返回您正在寻找的计数。
在这种情况下使用count(DISTINCT userid)
。