【问题标题】:Cumulative adding with dynamic base in Postgres在 Postgres 中使用动态基础进行累积添加
【发布时间】:2015-06-07 15:57:18
【问题描述】:

我在 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设置为适当的值。

【问题讨论】:

  • 很好的问题,包含所有必要的细节。请多提这样的问题。 :)

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


【解决方案1】:

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;

结果完全符合要求。

db小提琴here
sqlfiddle

通用聚合函数

要使其适用于任何阈值任何(数字)数据类型,并且还允许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;

db小提琴here
sqlfiddle

说明

在您的情况下,我们不必防御 NULL 值,因为 val 定义为 NOT NULL。如果NULL 可以参与,将f_sum_cap() 定义为STRICT,它可以工作,因为(per documentation):

如果状态转换函数被声明为“strict”,那么它不能 使用空输入调用。有了这样的转移函数,聚合 执行行为如下。具有任何空输入值的行是 忽略(该函数未被调用且先前的状态值为 保留)[...]

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

关于多态函数:

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

【讨论】:

  • 太棒了,谢谢欧文。一个问题:是否可以在运行时定义阈值(因为它并不总是 50)?谢谢:-)
  • 啊,我想我明白了,我已经更改了 f_sum_cap 函数以获取一个额外的 numeric 参数,该参数在查询时传递,例如sum_cap(val, 40) 然后让f_sum_cap 函数使用'select case when $1 &gt; $3 then 0 else $1 end + $2'。这是最好的方法吗?
  • @jabclab:基本上是的。在使用它的同时,我还将使其具有多态性并适合 NULL 值。考虑更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 2018-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
相关资源
最近更新 更多