Sql 我如何询问随时间变化的百分比

Sql 我如何询问随时间变化的百分比,sql,postgresql,count,percentage,window-functions,Sql,Postgresql,Count,Percentage,Window Functions,因此,我需要使用PostgreSQL,并询问COUNTDISTINCT用户ID在前7天的百分比变化 这可能吗 每天获取不同的用户非常简单: SELECT COUNT(DISTINCT userid), timestamp::date FROM logs GROUP BY timestamp::date ORDER BY timestamp::date DESC 如何将其转换为从今天到7天前的百分比?因此我们需要为第X天取一个值,为第X-7天取第二个值,然后计算百分比。 查询可能如下所示: S

因此,我需要使用PostgreSQL,并询问COUNTDISTINCT用户ID在前7天的百分比变化

这可能吗

每天获取不同的用户非常简单:

SELECT COUNT(DISTINCT userid), timestamp::date 
FROM logs
GROUP BY timestamp::date
ORDER BY timestamp::date DESC

如何将其转换为从今天到7天前的百分比?

因此我们需要为第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"
实际上,您不需要子查询或CTE。您可以使用窗口功能进行单选:

我使用ts作为列名而不是timestmap,因为使用ts作为标识符是不明智的

我安排了表演百分比的计算。这样我们就可以在不使用函数的情况下获得2个小数位数的舍入精度

窗口函数可以应用于同一查询级别的聚合函数,这就是为什么lagcountDISTINCT userid、7 OVER ORDER BY ts::date起作用的原因。 窗口函数超前和滞后采用附加参数。我选第七排

注意:这要求每天至少有一行,否则会计算错误。如果可能存在间隙,我将选择,只是没有CTE中的ORDER BY,它应该应用于外部查询。 或者,您可以使用generate_series创建一个天数列表,并将其左键联接到该列表中。就像这里展示的:

除以0 如果7天前没有行存在,则结果为空(对于@kordirko版本中的左连接以及滞后),这表示实际井数未知,并作为防止被0除的自动保护

但是,如果userid可以为NULL,则可以用0除法,我们需要捕捉这种情况。为什么会出现悖论效应

与其他聚合函数不同,count从不返回NULL。相反,空值只是不被计算

但是,如果7天前没有找到行,那么计数将为NULL,因为整个表达式为NULL:count甚至没有执行——在本例中,这恰好对我们非常有效

但是,如果找到一个或多个行,但userid为NULL,则计数为0,这将引发除0的异常

对于普通的countuserid,我们可以使用count*来防止这种情况。但这对于countDISTINCT用户ID来说是不可能的,并且可能返回也可能不返回您要查找的计数


在本例中使用NULLIFcountDISTINCT userid,0。

是否要查找最近七天内活动用户的百分比?或者日志开始后每周活跃用户的百分比?今天活跃用户的百分比与7天前特定日期活跃用户的百分比相比。这肯定是更好的答案,也是一个很好的方法,可以看到随着时间的推移,活跃用户的百分比
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;