在 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 切换rank
和timestamp
即可。也许一些舍入错误? 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 中检测趋势的聚合函数的主要内容,如果未能解决你的问题,请参考以下文章