计算每个影响者随时间的追随者增长

5rgfhyps  于 2021-07-26  发布在  Java
关注(0)|答案(2)|浏览(337)

我有一张表,上面有影响者和他们每天的追随者:

influencer_id |     date     |    followers
     1        | 2020-05-29   |      7361
     1        | 2020-05-28   |      7234
                    ...
     2        | 2020-05-29   |       82
     2        | 2020-05-28   |       85
                    ...
     3        | 2020-05-29   |      3434
     3        | 2020-05-28   |      2988
     3        | 2020-05-27   |      2765
                    ...

假设我想计算每个影响者在过去7天内获得了多少追随者,并得到下表:

influencer_id |                       growth
     1        |  <num followers last day - num followers first day>
     2        |                         "
     3        |                         "

作为第一次尝试,我这样做了:

SELECT influencer_id,
      (MAX(followers) - MIN(followers)) AS growth
FROM influencer_follower_daily
WHERE date < '2020-05-30'
AND date >= '2020-05-23'
GROUP BY influencer_id;

这是有效的,并显示了每一个影响者在一周内的增长。但它假设追随者的数量总是增加,人们永远不会放弃!
那么,有没有一种方法可以在原始表上使用sql查询来实现我想要的呢?或者我必须使用 FOR 计算每个日期之间的+/-更改列的循环?

mctunoxg

mctunoxg1#

简单聚合函数 first() 以及 last() 在标准postgres中没有实现。但见下文。

1. 数组\u agg()

戈登演示了一个问题 array_agg() ,但这比必要的成本更高,尤其是对于每个组有许多行的情况。更重要的是,打了两次电话 ORDER BY 每个骨料。这种等效的替代方法应该更快:

SELECT influencer_id, arr[array_upper(arr, 1)] - arr[1]
FROM  (
   SELECT influencer_id, array_agg(followers) AS arr
   FROM  (
      SELECT influencer_id, followers
      FROM   influencer_follower_daily
      WHERE  date >= '2020-05-23'
      AND    date <  '2020-05-30'
      ORDER  BY influencer_id, date
      ) sub1
   GROUP  BY influencer_id
   ) sub2;

因为它一次分类,一次聚合。内部子查询的排序顺序 sub1 被带入下一个层次。请参见:
如何将order by和limit与聚合函数结合使用?
索引很重要:
如果查询整个表或其中大部分,则 (influencer_id, date, followers) 可以帮助(很多)索引只扫描。
如果只查询表的一小部分,则 (date) 或者 (date, influencer_id, followers) 我能帮上忙(很多)。

2. 不同窗口函数(&W)

戈登还展示了 DISTINCT 具有窗口功能。同样,可以大大加快:

SELECT DISTINCT ON (influencer_id)
       influencer_id
     , last_value(followers) OVER (PARTITION BY influencer_id ORDER BY date
                                   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
     - followers AS growth
FROM   influencer_follower_daily
WHERE  date >= '2020-05-23'
AND    date <  '2020-05-30'
ORDER  BY influencer_id, date;

使用单个窗口函数,使用相同的排序顺序(!)作为主查询。为了实现这一点,我们需要使用 ROWS BETWEEN ... 请参见:
每行具有最大和最小日期加上关联id的postgresql查询
以及 DISTINCT ON 而不是 DISTINCT . 请参见:
按组选择每组中的第一行?

3. 自定义聚合函数

first()和last()

你可以自己加,很简单。请参阅postgres wiki中的说明。
或安装附加模块 first_last_agg 在c语言中实现得更快。
相关:
使用类似top和group by的方法
然后您的查询变得更简单:

SELECT influencer_id, last(followers) - first(followers) AS growth
FROM  (
   SELECT influencer_id, followers
   FROM   influencer_follower_daily 
   WHERE  date >= '2020-03-02'
   AND    date <  '2020-05-09'
   ORDER  BY influencer_id, date
   ) z
GROUP  BY influencer_id
ORDER  BY influencer_id;

自定义聚合增长()

你可以合并 first() 以及 last() 在单个聚合函数中。这会更快,但是调用两个c函数仍然会比一个自定义sql函数的性能更好。
基本上将我的第一个查询的逻辑封装在自定义聚合中:

CREATE OR REPLACE FUNCTION f_growth(anyarray)
  RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1[array_upper($1, 1)] - $1[1]';

CREATE OR REPLACE AGGREGATE growth(anyelement) (
   SFUNC     = array_append
 , STYPE     = anyarray
 , FINALFUNC = f_growth
 , PARALLEL  = SAFE
);

适用于任何数字类型(或带有运算符的任何类型) type - type 返回相同类型)。查询更简单,但:

SELECT influencer_id, growth(followers)
FROM  (
   SELECT influencer_id, followers
   FROM   influencer_follower_daily 
   WHERE  date >= '2020-05-23'
   AND    date <  '2020-05-30'
   ORDER  BY influencer_id, date
   ) z
GROUP  BY influencer_id
ORDER  BY influencer_id;

或者稍微慢一点,但最终很短:

SELECT influencer_id, growth(followers ORDER BY date)
FROM   influencer_follower_daily 
WHERE  date >= '2020-05-23'
AND    date <  '2020-05-30'
GROUP  BY 1
ORDER  BY 1;

db<>在这里摆弄

4. 每组多行的性能优化

对于每个组/分区有许多行的情况,其他查询技术可以更快(很多)。这些技巧:
按查询优化分组以检索每个用户的最新行
如果适用的话,我建议您开始一个新的问题,公开确切的表定义和基数。。。
密切相关:
从每组的第一行和最后一行获取值
postgresql:在group by子句中连接数组
使用类似top和group by的方法
对分组列中的重复值进行采样的最佳性能

disho6za

disho6za2#

postgres没有 first() / last() 聚合函数。一种方法是:

SELECT DISTINCT influencer_id,
       ( FIRST_VALUE(followers) OVER (PARTITION BY influencer_id ORDER BY DATE DESC) -
         FIRST_VALUE(followers) OVER (PARTITION BY influencer_id ORDER BY DATE ASC)
       ) as growth
FROM influencer_follower_daily
WHERE date < '2020-05-30' AND date >= '2020-05-23';

另一种选择是使用数组:

SELECT influencer_id,
       ( ARRAY_AGG(followers ORDER BY DATE DESC) )[1] -
         ARRAY_AGG(followers ORDER BY DATE ASC) )[1]
       ) as growth
FROM influencer_follower_daily
WHERE date < '2020-05-30' AND date >= '2020-05-23'
GROUP BY influencer_id;

相关问题