从表格开始
付款
+------------------------------+
|客户 ID |金额 |项目 |
| 5 | 10 |书 |
| 5 | 71 |鼠标 |
| 7 | 13 |封面|
| 7 | 22 |电缆 |
| 7 | 19 |书 |
+------------------------------+
SELECT customer_id,
AVG(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
我们得到
+---------------------------------+
|客户 ID |平均金额 |项目 |
| 5 | 40.5 |书 |
| 5 | 40.5 |鼠标 |
| 7 | 18 |封面|
| 7 | 18 |电缆 |
| 7 | 18 |书 |
+---------------------------------+
AVG 是一个聚合函数,它可以作为一个窗口函数。然而,并不是所有的窗口函数都是聚合函数。聚合函数是非复杂的窗口函数。
在上面的查询中,我们不要使用内置的AVG 函数,而是使用我们自己的实现。做同样的事情,只是由用户实现。上面的查询变成:
SELECT customer_id,
my_avg(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
与前一个查询的唯一区别是AVG 已替换为my_avg。我们现在需要实现我们的自定义函数。
关于如何计算平均值
将所有元素相加,然后除以元素个数。对于customer_id,共7 个,那就是(13 + 22 + 19) / 3 = 18。
我们可以分为:
关于聚合函数如何得到结果
平均分步计算。只有最后一个值是必需的。
从初始值 0 开始。
- Feed 13。计算中间/累加和,即 13。
- Feed 22. 计算累加总和,需要前一个总和加上这个元素:
13 + 22 = 35
- Feed 19. 计算累加总和,它需要前一个总和加上这个元素:
35 + 19 = 54。这是需要除以元素数 (3) 的总数。
- 第 3 步的结果被馈送到另一个函数,该函数知道如何将累加和除以元素数
这里发生的情况是状态从初始值 0 开始,每一步都在改变,然后传递到下一步。
只要有数据,状态就会在步骤之间移动。当所有数据都被消耗完时,状态进入最终功能(终端操作)。我们希望状态包含累加器以及终端操作所需的所有信息。
在计算平均值的特定情况下,终端操作需要知道累加器处理了多少个元素,因为它需要除以那个。出于这个原因,状态需要包括累计和和元素的数量。
我们需要一个包含两者的元组。预定义的POINT PostgreSQL 类型来救援。 POINT(5, 89) 表示 5 个元素的累加和,其值为 89。初始状态将为 POINT(0,0)。
累加器在所谓的状态函数中实现。终端操作在所谓的 final 函数中实现。
在定义自定义聚合函数时我们需要指定:
- 聚合函数名称和返回类型
- 初始状态
- 基础架构将在步骤之间传递到最终函数的状态类型
- 一个状态函数——知道如何执行累加步骤
- 最后一个函数——知道如何执行终端操作。并不总是需要(例如,在 SUM 的自定义实现中,累积和的最终值就是结果。)
这是自定义聚合函数的定义。
CREATE AGGREGATE my_avg (NUMERIC) ( -- NUMERIC is what the function returns
initcond = '(0,0)', -- this is the initial state of type POINT
stype = POINT, -- this is the type of the state that will be passed between steps
sfunc = my_acc, -- this is the function that knows how to compute a new average from existing average and new element. Takes in the state (type POINT) and an element for the step (type NUMERIC)
finalfunc my_final_func -- returns the result for the aggregate function. Takes in the state of type POINT (like all other steps) and returns the result as what the aggregate function returns - NUMERIC
);
剩下的就是定义两个函数my_acc和my_final_func。
CREATE FUNCTION my_acc (state POINT, elem_for_step NUMERIC) -- performs accumulated sum
RETURNS POINT
LANGUAGE SQL
AS $$
-- state[0] is the number of elements, state[1] is the accumulated sum
SELECT POINT(state[0]+1, state[1] + elem_for_step);
$$;
CREATE FUNCTION my_final_func (POINT) -- performs devision and returns final value
RETURNS NUMERIC
LANGUAGE SQL
AS $$
-- $1[1] is the sum, $1[0] is the number of elements
SELECT ($1[1]/$1[0])::NUMERIC;
$$;
现在函数可用了,上面定义的CREATE AGGREGATE 将成功运行。现在我们已经定义了聚合,可以运行基于my_avg 而不是内置AVG 的查询:
SELECT customer_id,
my_avg(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
结果与使用内置AVG 时得到的结果相同。
PostgreSQL 文档建议用户仅限于实现用户定义的聚合函数:
除了这些[预定义的窗口]函数之外,任何内置或用户定义的通用或统计聚合(即非有序集或假设集聚合)都可以用作窗口函数;
我怀疑ordered-set or hypothetical-set aggregates 的意思是:
- 返回的值与所有其他行相同(例如
AVG 和 SUM。相比之下,RANK 根据更复杂的标准为组中的所有行返回不同的值)
- 分区时 ORDER BY 没有任何意义,因为无论如何所有行的值都是相同的。相比之下,我们在使用
RANK() 时要ORDER BY
查询:
SELECT customer_id, item, rank() OVER (PARTITION BY customer_id ORDER BY amount desc) FROM payments;
几何平均数
以下是一个用户定义的聚合函数,我发现它没有内置聚合,可能对某些人有用。
状态函数计算项的自然对数的平均值。
最终函数将常量 e 提升到累加器提供的任何值。
CREATE OR REPLACE FUNCTION sum_of_log(state POINT, curr_val NUMERIC)
RETURNS POINT
LANGUAGE SQL
AS $$
SELECT POINT(state[0] + 1,
(state[1] * state[0]+ LN(curr_val))/(state[0] + 1));
$$;
CREATE OR REPLACE FUNCTION e_to_avg_of_log(POINT)
RETURNS NUMERIC
LANGUAGE SQL
AS $$
select exp($1[1])::NUMERIC;
$$;
CREATE AGGREGATE geo_mean (NUMBER)
(
stype = NUMBER,
initcond = '(0,0)', -- represent POINT value
sfunc = sum_of_log,
finalfunc = e_to_avg_of_log
);