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

Cumulative adding with dynamic base in Postgres

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

我有一个 table 这种格式:

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

然后我得到的是另一个 table 中的一个 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 设置为适当的值。

Create your own aggregate function,可作为window函数使用。

专门的聚合函数

这比人们想象的要容易:

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

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;

结果完全符合要求。

db<>fiddle here
sqlfiddle

通用聚合函数

使其适用于任何阈值任何(数字)数据类型,以及允许NULL:

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

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;

db<>fiddle here
sqlfiddle

说明

在您的情况下,我们不必防御 NULL 值,因为 val 定义为 NOT NULL。如果可以涉及 NULL,则将 f_sum_cap() 定义为 STRICT 并且它有效,因为 (per documentation):

If the state transition function is declared "strict", then it cannot be called with null inputs. With such a transition function, aggregate execution behaves as follows. Rows with any null input values are ignored (the function is not called and the previous state value is retained) [...]

函数和集合都多了一个参数。对于 polymorphic 变体,它可以是硬编码数据类型或与前导参数相同的多态类型。

关于多态函数:

  • Initial array in function to aggregate multi-dimensional array

请注意使用 无类型字符串文字 ,而不是数字文字,后者默认为 integer!