在Postgres中使用动态基础累积添加

时间:2015-04-02 15:55:50

标签: sql postgresql aggregate-functions aggregate window-functions

我在Postgres中有以下场景(我使用 9.4.1 )。

我有一个这种格式的表:

create table test(
    id serial,
    val numeric not null,
    created timestamp not null default(current_timestamp),
    fk integer not null
);

我接下来的是另一个表中的threshold numeric字段,该字段应该用于标记test的每一行。对于>= threshold的每个值,我希望将该记录标记为true,但如果它是true,则应将该后续计数重置为0,例如

数据集:

insert into test(val, created, fk)
  (100, now() + interval '10 minutes', 5),
  (25,  now() + interval '20 minutes', 5),
  (30,  now() + interval '30 minutes', 5),
  (45,  now() + interval '40 minutes', 5),
  (10,  now() + interval '50 minutes', 5);

阈值为50我想得到输出:

100 -> true (as 100 > 50) [reset]
25  -> false (as 25 < 50)
30  -> true (as 25 + 30 > 50) [reset]
45  -> false (as 45 < 50)
10  -> true (as 45 + 10 > 50)

是否可以在单个SQL查询中执行此操作?到目前为止,我已尝试使用window function

select t.*,
       sum(t.val) over (
         partition by t.fk order by t.created
       ) as threshold_met
from test t
where t.fk = 5;

正如你所看到的,我已经达到累积频率的程度,并怀疑rows between x preceding and current row的调整可能是我正在寻找的。我只是不知道如何执行重置,即将上面的x设置为适当的值。

1 个答案:

答案 0 :(得分:3)

您可以create your own aggregate function,它可以用作窗口功能。

专业聚合函数

这比人们想象的容易:

CREATE OR REPLACE FUNCTION f_sum_cap50 (numeric, numeric)
  RETURNS numeric LANGUAGE sql AS
'SELECT CASE WHEN $1 > 50 THEN 0 ELSE $1 END + $2';

CREATE AGGREGATE sum_cap50 (numeric)
( sfunc     = f_sum_cap50
 ,stype     = numeric
 ,initcond  = 0
);

然后:

SELECT *, sum_cap50(val) OVER (PARTITION BY fk
                               ORDER BY created) > 50 AS threshold_met 
FROM   test
WHERE  fk = 5;

结果完全符合要求。

SQL Fiddle.

更通用的聚合函数

如果您希望其适用于其他阈值和/或其他数据类型以及允许NULL值,则可以进行更多操作通用

CREATE OR REPLACE FUNCTION f_sum_cap (anyelement, anyelement, anyelement)
  RETURNS anyelement  LANGUAGE sql STRICT AS
$$SELECT CASE WHEN $1 > $3 THEN '0' ELSE $1 END + $2;$$;

CREATE AGGREGATE sum_cap (anyelement, anyelement)
( sfunc     = f_sum_cap
 ,stype     = anyelement
 ,initcond  = '0'
);

然后,使用任何数字类型调用限制,例如110:

SELECT *
     , sum_cap(val, '110') OVER (PARTITION BY fk
                                 ORDER BY created) AS capped_at_110
     , sum_cap(val, '110') OVER (PARTITION BY fk
                                 ORDER BY created) > 110 AS threshold_met 
FROM   test
WHERE  fk = 5;

SQL Fiddle.

解释

  • 在您的情况下,我们无需防御 NULL 值,因为val已定义NOT NULL。如果可以涉及NULL,请将f_sum_cap()定义为 STRICT ,它可以正常运行,因为(per documentation):

      

    如果状态转换函数被声明为“严格”,则它不能   用null输入调用。有了这样的过渡功能,聚合   执行行为如下。具有任何空输入值的行是   忽略(该函数未被调用,之前的状态值为   保留)[...]

  • 函数和聚合都需要多一个参数。对于polymorphic变体,它可以是硬编码数据类型,也可以是与主要参数相同的多态类型。

  • 关于多态函数:

  • 请注意我如何使用无类型字符串文字,而不是数字文字,默认为integer