计算每个影响者随时间的追随者增长
Calculating follower growth over time for each influencer
我每天都有一个 table 有影响力的人和他们的追随者计数器:
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 天内获得了多少粉丝,并得到以下 table:
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;
这有效并显示了每个影响者在一周内的增长。但它假设关注者人数始终增加并且人们永远不会取消关注!
那么有没有一种方法可以通过对原始 table 的 SQL 查询来实现我想要的?或者我是否必须使用计算每个日期之间的 +/- 关注者更改列的 FOR
循环生成一个全新的 table?
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;
标准 Postgres 中未实现简单聚合函数 first()
和 last()
。但见下文。
1。 array_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
的排序顺序被带到下一级。参见:
索引 事项:
如果您查询整个 table 或其中的大部分,(influencer_id, date, followers)
上的 index 可以帮助(很多)仅索引扫描。
如果您只查询 table 的一小部分,(date)
或 (date, influencer_id, followers)
上的 index 可以帮助(很多)。
2。 DISTINCT
& window 函数
Gordon 还展示了 DISTINCT
和 window 功能。同样,可以显着更快:
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;
使用 single window 函数,使用与主查询相同的排序顺序 (!)。为此,我们需要非默认 window 定义 ROWS BETWEEN ...
请参阅:
和 DISTINCT ON
而不是 DISTINCT
。参见:
- Select first row in each GROUP BY group?
3。自定义聚合函数
first()
和 last()
你可以自己添加,很简单。参见 instructions in the Postgres Wiki。
或者安装 additional module first_last_agg
在 C 中实现更快
相关:
那么你的查询就变得更简单了:
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;
自定义聚合growth()
您可以将 first()
和 last()
组合在一个聚合函数中。这样速度更快,但调用两个 C 函数的性能仍优于一个自定义 SQL 函数。
基本上将我的第一个查询的逻辑封装在自定义聚合中:
CREATE OR REPLACE FUNCTION f_growth(anyarray)
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT [array_upper(, 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<>fiddle here
4。每组许多行的性能优化
每个组/分区有 许多 行,其他查询技术可以(很多)更快。沿着这些方向的技术:
- Optimize GROUP BY query to retrieve latest row per user
如果适用,我建议您提出一个新问题,披露确切的 table 定义和基数 ...
密切相关:
- Get values from first and last row per group
- PostgreSQL: joining arrays within group by clause
- Best performance in sampling repeated value from a grouped column
我每天都有一个 table 有影响力的人和他们的追随者计数器:
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 天内获得了多少粉丝,并得到以下 table:
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;
这有效并显示了每个影响者在一周内的增长。但它假设关注者人数始终增加并且人们永远不会取消关注!
那么有没有一种方法可以通过对原始 table 的 SQL 查询来实现我想要的?或者我是否必须使用计算每个日期之间的 +/- 关注者更改列的 FOR
循环生成一个全新的 table?
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;
标准 Postgres 中未实现简单聚合函数 first()
和 last()
。但见下文。
1。 array_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
的排序顺序被带到下一级。参见:
索引 事项:
如果您查询整个 table 或其中的大部分,
(influencer_id, date, followers)
上的 index 可以帮助(很多)仅索引扫描。如果您只查询 table 的一小部分,
(date)
或(date, influencer_id, followers)
上的 index 可以帮助(很多)。
2。 DISTINCT
& window 函数
Gordon 还展示了 DISTINCT
和 window 功能。同样,可以显着更快:
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;
使用 single window 函数,使用与主查询相同的排序顺序 (!)。为此,我们需要非默认 window 定义 ROWS BETWEEN ...
请参阅:
和 DISTINCT ON
而不是 DISTINCT
。参见:
- Select first row in each GROUP BY group?
3。自定义聚合函数
first()
和 last()
你可以自己添加,很简单。参见 instructions in the Postgres Wiki。
或者安装 additional module first_last_agg
在 C 中实现更快
相关:
那么你的查询就变得更简单了:
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;
自定义聚合growth()
您可以将 first()
和 last()
组合在一个聚合函数中。这样速度更快,但调用两个 C 函数的性能仍优于一个自定义 SQL 函数。
基本上将我的第一个查询的逻辑封装在自定义聚合中:
CREATE OR REPLACE FUNCTION f_growth(anyarray)
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT [array_upper(, 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<>fiddle here
4。每组许多行的性能优化
每个组/分区有 许多 行,其他查询技术可以(很多)更快。沿着这些方向的技术:
- Optimize GROUP BY query to retrieve latest row per user
如果适用,我建议您提出一个新问题,披露确切的 table 定义和基数 ...
密切相关:
- Get values from first and last row per group
- PostgreSQL: joining arrays within group by clause
- Best performance in sampling repeated value from a grouped column