在 PostgreSQL 中检测趋势的聚合函数

Posted

技术标签:

【中文标题】在 PostgreSQL 中检测趋势的聚合函数【英文标题】:Aggregate function to detect trend in PostgreSQL 【发布时间】:2014-03-29 03:26:23 【问题描述】:

我正在使用 psql DB 来存储这样的数据结构:

datapoint(userId, rank, timestamp)

其中 timestamp 是 Unix Epoch 毫秒时间戳

在这个结构中,我每天存储每个用户的排名,所以它就像:

UserId   Rank  Timestamp
1        1     1435366459
1        2     1435366458
1        3     1435366457
2        8     1435366456
2        6     1435366455
2        7     1435366454

因此,在上面的示例数据中,userId 1 的排名随着每次测量而提高,这意味着它具有积极的趋势,而 userId 2 的排名下降,这意味着它具有负面的趋势。

我需要做的是根据最近 N 次测量检测所有具有积极趋势的用户。

【问题讨论】:

我认为这是一个算法问题。我认为这可能会有所帮助:您最后一个数据点的平均排名,并比较最后一个排名是大于还是小于那个。 在您看来,积极的趋势到底是什么?您是否要求每个等级 R_n+1 严格小于其前任 R_n ?或者如果每一个都不比它的前任大就足够了?或者秩 R_n+N 小于 R_n 就足够了(忽略中间值)? 【参考方案1】:

一种方法是对每个用户的排名执行线性回归,并检查斜率是正数还是负数。幸运的是,PostgreSQL 有一个内置函数可以做到这一点 - regr_slope:

SELECT   user_id, regr_slope (rank1, timestamp1) AS slope
FROM     my_table
GROUP BY user_id

此查询为您提供基本功能。现在,如果你愿意,可以用case 表达式修饰一下:

SELECT user_id, 
       CASE WHEN slope > 0 THEN 'positive' 
            WHEN slope < 0 THEN 'negative' 
            ELSE 'steady' END AS trend
FROM   (SELECT   user_id, regr_slope (rank1, timestamp1) AS slope
        FROM     my_table
        GROUP BY user_id) t

编辑: 不幸的是,regr_slope 没有内置方法来处理“top N”类型要求,所以这应该单独处理,例如,通过带有row_number 的子查询:

-- Decoration outer query
SELECT user_id, 
       CASE WHEN slope > 0 THEN 'positive' 
            WHEN slope < 0 THEN 'negative' 
            ELSE 'steady' END AS trend
FROM   (-- Inner query to calculate the slope
        SELECT   user_id, regr_slope (rank1, timestamp1) AS slope
        FROM     (-- Inner query to get top N
                  SELECT user_id, rank1, 
                         ROW_NUMER() OVER (PARTITION BY user_id 
                                           ORDER BY timestamp1 DESC) AS rn
                  FROM   my_table) t
        WHERE    rn <= N -- Replace N with the number of rows you need
        GROUP BY user_id) t2

【讨论】:

我在 SQL Fiddle 中尝试了您的查询,但它始终为斜率返回 NULL。 sqlfiddle.com/#!15/924fd/2 谢谢!看起来像一个优雅而简单的解决方案,但我在我的测试用例上尝试过,甚至趋势明显是积极的,斜率小于 0 切换ranktimestamp 即可。也许一些舍入错误? sqlfiddle.com/#!15/924fd/27/0 虽然这会考虑所有行,但不仅仅是最后一个 n 确实,似乎是一些舍入问题 - 较小的值似乎可以工作。 您知道如何扩展您的查询以仅尊重最后的n 行吗?那时我会投票给它。对我来说似乎很优雅。【参考方案2】:

您可以为此使用分析函数。总体方法:

使用 lag() 计算上一个排名 用例判断趋势是否为正(0 或 1) 使用 min() 获取前 N 行的最小趋势;如果 N 行的趋势为正,则返回 1,否则返回 0。要将其限制为 N 行,请使用窗口函数的 between N preceding and 0 following 子句

代码:

select v2.*,
  min(positive_trend) over (partition by userid order by timestamp1
                             rows between 3 preceding and 0 following) as trend_overall
from (
  select v1.*,
    (case when prev_rank < rank1 then 0 else 1 end) as positive_trend
  from (
    select userid,
      rank1,
      timestamp1,
      lag(rank1) over (partition by userid order by timestamp1) as prev_rank
    from t1
    order by userid, timestamp1
  ) v1
) v2

SQL Fiddle

更新

要仅获取具有整体趋势的用户 ID 和排名的增量,您必须添加另一个调用 lag(.., N+1) 以获取前 n 个排名,并添加 row_number() 以获取同一用户 ID 内的编号:

select v3.userid, v3.trend_overall, delta_rank
from (  
  select v2.*,
    min(positive_trend) over (partition by userid order by timestamp1
                               rows between 3 preceding and 0 following) as trend_overall,
    latest_rank - prev_N_rank as delta_rank
  from (
    select v1.*,
      (case when prev_rank < rank1 then 0 else 1 end) as positive_trend,
      max(case when v1.rn = 1 then rank1 else NULL end) over (partition by userid) as latest_rank
    from (
      select userid,
        rank1,
        timestamp1,
        lag(rank1) over (partition by userid order by timestamp1) as prev_rank,
        lag(rank1, 4) over (partition by userid order by timestamp1) as prev_N_rank,
        row_number() over (partition by userid order by timestamp1 desc) as rn
      from t1
      order by userid, timestamp1
    ) v1
  ) v2
) v3 
where rn = 1
group by userid, trend_overall, delta_rank
order by userid, trend_overall, delta_rank

Updated SQL Fiddle

【讨论】:

感谢@Frank!这确实与我正在寻找的非常接近。但是有一件事,是否可以以这样的方式聚合结果,即每个 userId 只返回一行,趋势(1/0)和增量(N 等级 - 当前等级)?

以上是关于在 PostgreSQL 中检测趋势的聚合函数的主要内容,如果未能解决你的问题,请参考以下文章

具有分组依据的 PostgreSQL 聚合函数

PostgreSQL中的选择或布尔聚合函数

从 PostgreSQL 到 Cassandra - 不支持聚合函数

postgresql 聚合函数存储在哪里?

在 PostgreSQL 的窗口函数中按降序聚合排序

postgreSQL 选择聚合函数中未使用的附加列