【问题标题】:How to create a custom windowing function for PostgreSQL? (Running Average Example)如何为 PostgreSQL 创建自定义窗口函数? (运行平均示例)
【发布时间】:2012-11-27 05:19:11
【问题描述】:

我真的很想更好地了解在 PostgreSQL 中创建在 Windows 上运行的 UDF 所涉及的内容。我做了一些关于如何创建 UDF 一般的搜索,但没有找到一个如何在窗口上操作的示例。

为此,我希望有人愿意分享如何编写 UDF(可以是 C、pl/SQL 或 PostgreSQL 支持的任何程序语言)的代码,该代码计算一个窗口。我意识到有一些方法可以通过使用窗口语法(我相信语法之间的行)应用标准平均聚合函数来做到这一点,我只是要求这个功能,因为我认为它是一个很好的简单示例。另外,我认为如果有一个平均函数的窗口版本,那么数据库可以保持运行总和和观察计数,并且不会在每次迭代时总结几乎相同的行集。

【问题讨论】:

  • 在窗口定义中使用order by 时,您可以轻松获得运行平均值。不需要使用“rows between”:sqlfiddle.com/#!12/902c3/1
  • 顺便说一句,“pl/SQL”是 Oracle 的过程语言。你可能会想到“PL/pgSQL”。

标签: postgresql user-defined-functions


【解决方案1】:

您必须查看 postgresql 源代码 postgresql/src/backend/utils/adt/windowfuncs.c 和 postgresql/src/backend/executor/nodeWindowAgg.c

没有好的文档 :( -- 功能齐全的窗口函数只能在 C 或 PL/v8 中实现 - 没有其他语言的 API。

http://www.pgcon.org/2009/schedule/track/Version%208.4/128.en.html PostgreSQL 实现作者的演示文稿。

我发现只有一个非核心实现 - http://api.pgxn.org/src/kmeans/kmeans-1.1.0/

http://pgxn.org/dist/plv8/1.3.0/doc/plv8.html

【讨论】:

  • 这个在链接的deck里有引用,但下面是直接链接(c源代码作为附件列出)。 postgresql.org/message-id/…
  • 为了完整起见,PL/R中有支持
【解决方案2】:

PL/R 提供了这样的功能。有关一些示例,请参阅here。也就是说,我不确定它(当前)是否满足您“保持 [ing] 运行总和和观察计数并且 [not] sum[ming] 在每次迭代中几乎相同的行集”的要求(参见 @987654322 @)。

【讨论】:

    【解决方案3】:

    根据documentation用户可以添加其他窗口函数。此外,任何内置或用户定义的普通聚合函数都可以用作窗口函数。”(第 4.2.8 节)。这对我计算股票分割调整很有用:

    CREATE OR REPLACE FUNCTION prod(float8, float8) RETURNS float8
      AS 'SELECT $1 * $2;'
      LANGUAGE SQL IMMUTABLE STRICT;
    
    CREATE AGGREGATE prods ( float8 ) (
      SFUNC = prod,
      STYPE = float8,
      INITCOND = 1.0
    );
    
    create or replace view demo.price_adjusted as
      select id, vd,
        prods(sdiv) OVER (PARTITION by id ORDER BY vd DESC ROWS UNBOUNDED PRECEDING) as adjf,
        rawprice * prods(sdiv) OVER (PARTITION by id ORDER BY vd DESC ROWS UNBOUNDED PRECEDING) as price
      from demo.prices_raw left outer join demo.adjustments using (id,vd);
    

    这是两个表的架构:

    CREATE TABLE demo.prices_raw (
      id VARCHAR(30),
      vd DATE,
      rawprice float8 );
    
    CREATE TABLE demo.adjustments (
      id VARCHAR(30),
      vd DATE,
      sdiv float);
    

    【讨论】:

      【解决方案4】:

      从表格开始

      付款 +------------------------------+ |客户 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 开始。

      1. Feed 13。计算中间/累加和,即 13。
      2. Feed 22. 计算累加总和,需要前一个总和加上这个元素:13 + 22 = 35
      3. Feed 19. 计算累加总和,它需要前一个总和加上这个元素:35 + 19 = 54。这是需要除以元素数 (3) 的总数。
      4. 第 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_accmy_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 的意思是:

      • 返回的值与所有其他行相同(例如 AVGSUM。相比之下,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
      );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-28
        • 2011-06-16
        • 2017-05-14
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多