Postgresql 用户定义的聚合函数计数

Posted

技术标签:

【中文标题】Postgresql 用户定义的聚合函数计数【英文标题】:Postgresql user-defined aggregate function count 【发布时间】:2015-07-06 09:54:36 【问题描述】:

我正在定义时变整数,即时变整数段的数组,后者是与时间戳范围相关联的整数值。

CREATE TYPE integerTS AS (val integer, p tsrange);
CREATE TYPE integerTT AS (traj integerTS[]);

这种值的一个例子是

select integerTT(ARRAY[
integerTS(1, '2012-01-01 08:00:00', '2012-01-01 08:10:00'),
integerTS(2, '2012-01-01 08:10:00', '2012-01-01 08:20:00')
])

我可以定义 min、max 和 sum 聚合函数,但在这些类型上,例如

WITH Values AS (
SELECT integerTT(ARRAY[
    integerTS(3, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(2, '2012-01-01 08:10:00', '2012-01-01 08:30:00')])
UNION
SELECT integerTT(ARRAY[
    integerTS(1, '2012-01-01 08:20:00', '2012-01-01 08:40:00')])
)
SELECT min(val)
from Values

结果

integerTT(ARRAY[
    integerTS(3, '2012-01-01 08:00:00', '2012-01-01 08:10:00'),
    integerTS(2, '2012-01-01 08:10:00', '2012-01-01 08:20:00'),
    integerTS(1, '2012-01-01 08:20:00', '2012-01-01 08:40:00')])

但是,我无法为时变整数定义计数聚合函数。 我开始定义函数如下。

CREATE OR REPLACE FUNCTION count(tt1 integerTT, tt2 integerTT) RETURNS integerTT AS
$BODY$
DECLARE 
BEGIN
    -- 0 is a dummy value
    return integerTT(count(integerTS(0,getT(tt1)), integerTS(0,getT(tt2))));
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION count(ts1 integerTS, ts2 integerTS) RETURNS integerTS[] AS
$BODY$
DECLARE 
    intersection tsrange;
    result integerTS[];
BEGIN
    intersection := getT(ts1) * getT(ts2);
    IF isempty(intersection) THEN
        IF getT(ts1) << getT(ts2) THEN
            result := ARRAY[integerTS(1,getT(ts1)),integerTS(1,getT(ts2))];
        ELSE 
            result := ARRAY[integerTS(1,getT(ts2)),integerTS(1,getT(ts1))];
        END IF;
    ELSE 
        IF lower(getT(ts1)) < lower(intersection) THEN
        result := array_append(result,integerTS(1,tsrange(lower(getT(ts1)), lower(intersection))));
        END IF;
        IF lower(getT(ts2)) < lower(intersection) THEN
        result := array_append(result,integerTS(1,tsrange(lower(getT(ts2)), lower(intersection))));
        END IF;
        result := array_append(result,integerTS(2,intersection));   
        IF upper(intersection) < upper(getT(ts1)) THEN
        result := array_append(result,integerTS(1,tsrange(upper(intersection), upper(getT(ts1)))));
        END IF;
        IF upper(intersection) < upper(getT(ts2)) THEN
        result := array_append(result,integerTS(1,tsrange(upper(intersection), upper(getT(ts2)))));
        END IF;
    END IF;
    RETURN result;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

然后

WITH Values AS (
SELECT integerTT(ARRAY[
    integerTS(3, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(2, '2012-01-01 08:10:00', '2012-01-01 08:30:00')])
UNION
SELECT integerTT(ARRAY[
    integerTS(1, '2012-01-01 08:20:00', '2012-01-01 08:40:00')])
)
SELECT count(val)
from Values

得到正确的值如下

integerTT(ARRAY[
    integerTS(1, '2012-01-01 08:00:00', '2012-01-01 08:10:00'),
    integerTS(2, '2012-01-01 08:10:00', '2012-01-01 08:30:00'),
    integerTS(1, '2012-01-01 08:30:00', '2012-01-01 08:40:00')])

但是,当聚合函数定义如下

CREATE AGGREGATE count (integerTT)
(
    sfunc = count,
    stype = integerTT
);

不起作用,因为它总是返回 1 和 2 个计数值。例如

WITH Values AS (
SELECT integerTT(ARRAY[
    integerTS(3, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(4, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(5, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(6, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
UNION
SELECT integerTT(ARRAY[
    integerTS(7, '2012-01-01 08:00:00', '2012-01-01 08:20:00')]) as val
)
SELECT count(val)
from Values

返回

integerTT(ARRAY[
    integerTS(2, '2012-01-01 08:00:00', '2012-01-01 08:20:00')])

虽然它应该返回

integerTT(ARRAY[
    integerTS(5, '2012-01-01 08:00:00', '2012-01-01 08:20:00')])

如何定义时变计数?我使用的是 PostgreSQL 版本 9.4.1。

【问题讨论】:

【参考方案1】:

虽然这不是您实际要求的答案,但这是我实际给出的答案。评论太复杂了。

仅仅因为您可以创建一个类型,它是由整数和范围类型组成的复合类型的数组,甚至在此之上创建聚合函数......并不意味着它是这样做是个好主意。

integerTT 实现为实际表更简单、更快、更不容易出错并且需要更少的磁盘空间:

CREATE tbl (
  tbl_id serial PRIMARY KEY
, batch_id int  -- to wrap a set of "integerTS"
, val integer   -- your "integerTS" decomposed
, p tsrange     -- your "integerTS" decomposed
);

然后使用现有(更快)聚合函数编写普通 SQL 查询。

【讨论】:

以上是关于Postgresql 用户定义的聚合函数计数的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql - 窗口函数聚合

postgreSQL 选择聚合函数中未使用的附加列

在 PostgreSQL 中检测趋势的聚合函数

何时合并发生在Spark中的用户定义聚合函数UDAF中

具有分组依据的 PostgreSQL 聚合函数

PostgreSQL中的选择或布尔聚合函数