【问题标题】:Custom Aggregate in postgres using multiple columns使用多列在postgres中自定义聚合
【发布时间】:2018-03-30 11:07:26
【问题描述】:

免责声明:以下解决方案

我有一组来自查询的记录,其中包含 (idx, time, category, weight, distance) 列:

  • idx 是描述某种关系的 INTEGER
  • timeTIMESTAMP WITHOUT TIMEZONE,可以(几乎)采用任意值,但每个值都会出现多次(对于每个 idxcategory
  • categoryVARCHAR 和一个分类变量;它的价值是有限的,并且会经常重复发生
  • weightDOUBLE PRECISION
  • distance 是一些预先计算的值

行可能如下所示:

(1, '2017-01-01 00:00', 'class_a', 1, 234.5)
(1, '2017-01-01 00:00', 'class_a', 1, 987.1)
(1, '2017-01-01 00:00', 'class_a', 1, 1.23)
(1, '2017-01-01 00:00', 'class_b', 1, 48.5)
(2, '2017-01-01 00:00', 'class_a', 1, 8763.5)
(1, '2017-01-01 00:13', 'class_a', 1, 598.02)
(1, '2017-01-01 00:13', 'class_b', 1, 76.9)
...
(2, '2017-01-27 21:07', 'class_b', 1, 184.0)

有什么问题?

我正在寻找一种解决方案来计算此类数据的自定义聚合,但我几乎找不到任何说明或示例说明这是如何实际完成的(希望无需编写 C 扩展postgres)。

SELECT
  idx, time, category,
  weighted_density(
    value, distance, 10000.0 -- arbitrary 10k is explained below
  ) AS wd
FROM (my rows as shown above)
GROUP BY
  idx, time, category

我觉得设置一个自定义聚合(这里命名为WEIGHTED_DENSITY)应该是实现类似于概述查询的正确方法。我的目标是最终得到一个结果集,其中复合 (idx, time, category) 是唯一的,并且其 wd 是使用相关行中的所有 weightdistance 值计算得出的。

免责声明:以下解决方案

到目前为止我尝试了什么?

首先,我从数据库中获取整个行并使用另一种程序和语言 (python)离线 计算聚合。但这是相当消耗资源的,应该在数据库服务器而不是本地机器上运行(也是为了确保完整性)。

然后,我尝试设置一个 postgres 函数来使用单行计算结果值:

CREATE OR REPLACE FUNCTION _gaussian_density(
    IN DOUBLE PRECISION, -- the weight
    IN DOUBLE PRECISION, -- the distance
    IN DOUBLE PRECISION  -- the maximum distance
  ) RETURNS DOUBLE PRECISION AS
$BODY$
BEGIN
  -- calculate weighted density, using max distance;
  -- this calculation itself doesn't really matter; it's some sort
  -- of density using a cropped gaussian kernel, for those who ask.
  RETURN
    CASE
      WHEN ABS($2) > ABS($3) THEN 0.0
      WHEN ABS($2) <= 0.0 THEN 1.0
      ELSE
        $1 * (
          1.0 / |/ (2.0 * PI())
        ) * POWER(EXP(-1 * (3.0 * ABS($2) / ABS($3))), 2)
        / 0.4
    END;
END
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 10;

另外,为了使函数作为聚合可用,我尝试了

CREATE AGGREGATE weighted_density(DOUBLE PRECISION, DOUBLE PRECISION)
(
    sfunc = _gaussian_density,
    stype = DOUBLE PRECISION,
    initcond = 0.0
);

但这就是我卡住的地方,我只是无法正确处理,似乎我需要一个示例或一点点提示,将我推向正确的方向,了解如何正确创建和使用自定义聚合的。

向你们干杯,并提前非常感谢!

解决方案

感谢@klin 指出我错过了携带聚合状态。现在,这终于奏效了:

CREATE FUNCTION _gaussian_density(
    weight FLOAT8,
    distance FLOAT8,
    maxdist FLOAT8
  )
RETURNS FLOAT8
IMMUTABLE
CALLED ON NULL INPUT
LANGUAGE plpgsql
AS $$
  DECLARE
    abs_weight FLOAT8;
    abs_distance FLOAT8;
    abs_maxdist FLOAT8;
    dist_weight FLOAT8;
  BEGIN
    -- calculate weighted density, using max distance;
    -- this calculation itself doesn't really matter; it's some sort
    -- of density using a cropped gaussian kernel, for the curious
    abs_weight := ABS(COALESCE(weight, 1.0));
    abs_distance := ABS(COALESCE(distance, 0.0));
    abs_maxdist := ABS(COALESCE(maxdist, 0.0));
    IF abs_distance > abs_maxdist THEN RETURN 0.0; END IF;
    IF abs_distance <= 0.0 THEN RETURN 1.0 * abs_weight; END IF;
    RETURN abs_weight * (
            1.0 / |/ (2.0 * PI())
          ) * POWER(EXP(-1 * (3.0 * abs_distance / abs_maxdist)), 2)
          / 0.4;
  END;
$$;

CREATE FUNCTION _gaussian_statetransition(
    agg_state FLOAT8, -- carry the state!
    weight FLOAT8,
    distance FLOAT8,
    maxdist FLOAT8)
RETURNS FLOAT8
IMMUTABLE
LANGUAGE plpgsql
AS $$
  BEGIN
    RETURN
      agg_state + _gaussian_density(weight, distance, maxdist);
  END;
$$;

CREATE AGGREGATE weighted_density(FLOAT8, FLOAT8, FLOAT8)
(
    sfunc = _gaussian_statetransition,
    stype = FLOAT8,
    initcond = 0
);

我希望仍然能够在聚合之外使用密度计算函数,因此我决定为状态转换添加另一个函数,该函数又使用函数_gaussian_density

然后聚合定义状态类型及其初始状态,我们就可以开始了。为了正确处理一些边缘情况,我稍微调整了_gaussian_density(也用于处理NULL 值)..

非常感谢!

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    函数_gaussian_density() 应该依赖于上一步计算的值。如果在您的情况下这是第一个参数weight,那么初始条件不应为 0,因为所有接下来的计算结果都将为零。我假设weight的初始值为1.0:

    DROP AGGREGATE weighted_density(DOUBLE PRECISION, DOUBLE PRECISION);
    CREATE AGGREGATE weighted_density(DOUBLE PRECISION, DOUBLE PRECISION)
    (
        sfunc = _gaussian_density,
        stype = DOUBLE PRECISION,
        initcond = 1.0 -- !!
    );
    

    请注意,聚合不使用表的列weight,因为它是内部状态值,只应声明初始条件并作为最终结果返回。

    SELECT
        idx, time, category,
        weighted_density(distance, 10000) AS wd -- !!
    FROM my_table
    GROUP BY idx, time, category  
    ORDER BY idx, time, category;
    
     idx |        time         | category |         wd          
    -----+---------------------+----------+---------------------
       1 | 2017-01-01 00:00:00 | class_a  |   0.476331421206002
       1 | 2017-01-01 00:00:00 | class_b  |   0.968750868953701
       1 | 2017-01-01 00:13:00 | class_a  |    0.69665860026144
       1 | 2017-01-01 00:13:00 | class_b  |   0.952383202706387
       2 | 2017-01-01 00:00:00 | class_a  | 0.00519142111518706
       2 | 2017-01-27 21:07:00 | class_b  |   0.893107967346503
    (6 rows)    
    

    我不确定我是否正确阅读了您的意图,但是我的言论应该会让您走上正确的道路。

    【讨论】:

    • 哦,谢谢指出状态需要随身携带。我现在开始工作了;将用我提出的解决方案更新我的问题。非常感谢,伙计!
    猜你喜欢
    • 2016-10-10
    • 1970-01-01
    • 2022-12-16
    • 1970-01-01
    • 2022-01-06
    • 2019-11-04
    • 2021-06-25
    • 2017-05-19
    • 1970-01-01
    相关资源
    最近更新 更多