0

我正在 PostgreSQL 中编写存储过程。该算法应该处理一个二维double precision数字数组。

据我调查,Postgres 中的数组操作是通用的并且相当繁重。我试图证明的简单示例具有过高的计算成本。

例子:

CREATE OR REPLACE FUNCTION fill_2d_array( rows integer, cols integer) 
  RETURNS integer AS
$BODY$

DECLARE

img double precision[][];

i integer; j integer;
cont integer;

BEGIN

img  := ARRAY( SELECT 0 FROM generate_series(1, filas * columnas) ) ; 
cont:= 0;
For i IN 1..rows LOOP
    For j IN 1..cols LOOP
        img[i * cols + j] := (i * cols + j)::double precision;
        cont := cont + 1;
    END LOOP;
END LOOP;

return cont;
END;
$BODY$
  LANGUAGE plpgsql;

有人可以帮我找到处理二维数组的替代路径或改进吗?

4

1 回答 1

2

程序功能

基本问题

  • 声明数组变量的维度,就像float8[][]二维数组一样,仅用于文档。考虑这个相关答案中的细节:
    mapping postgresql text[][] type and Java type

  • 您正在混淆一维和二维数组。在声明二维数组(无效)时,您只能将其设为一维数组。

  • 要初始化数组,请使用array_fill()

    img := array_fill(0, ARRAY[rows,cols])
    

    此示例生成一个二维数组 - 与您的错误语句相反,生成一个一维数组:

    img  := ARRAY( SELECT 0 FROM generate_series(1, rows* cols) );
  • 显示的数组下标img[i * cols + j]几乎没有意义。最大值将是您初始化的两倍,从而导致“越界”错误。我想你的意思是img[i][j]

工作版本

所有东西放在一起它可以像这样工作:

CREATE OR REPLACE FUNCTION f_array_fill(rows integer, cols integer
                                                    , OUT img float8[][]) AS
$func$
DECLARE
   i  int;
   j  int;
BEGIN

img := array_fill(0, ARRAY[rows,cols]);

FOR i IN 1 .. rows LOOP
    FOR j IN 1 .. cols LOOP
        img[i][j] := (i * cols + j)::float8;
    END LOOP;
END LOOP;

END
$func$ LANGUAGE plpgsql;

称呼:

SELECT f_array_fill(2,3);

结果:

{{4,5,6},{7,8,9}}

要使函数有用,请返回生成的数组。为此使用OUT参数

优越的基于集合的版本

plpgsql 中的循环和单独分配相对较慢。正如@Craig 在此相关答案中所解释的那样,数组处理的性能特别差:
为什么 PostgreSQL 数组访问在 C 中比在 PL/pgSQL 中快得多?

我会改用基于集合的操作,使用更大的数字会更快。

多维数组的聚合函数

要生成多维数组,我们需要一个自定义聚合函数。array_agg()或者数组构造函数只生成一维数组。这很简单,正如我们在这个相关答案中得出的那样:
Initial array in function to aggregate multi-dimensional array

CREATE AGGREGATE array_agg_mult (anyarray)  (
    SFUNC     = array_cat
   ,STYPE     = anyarray
   ,INITCOND  = '{}'
);

替代功能

使用这种美感,我们可以构建一个与上面相同的简单 SQL 函数:

CREATE OR REPLACE FUNCTION f_array_fill_sql(_rows integer, _cols integer)
  RETURNS float8[][] AS
$func$
SELECT array_agg_mult(ARRAY[arr1]) AS arr2
FROM  (
   SELECT array_agg((i * $2 + j)::float8) AS arr1
   FROM   generate_series(1, $1) i
   CROSS  JOIN generate_series(1, $2) j
   GROUP  BY i
   ORDER  BY i
   ) sub
$func$ LANGUAGE sql

称呼:

SELECT f_array_fill_sql(3,4);

结果:

{{4,5,6},{7,8,9}}

相比

对于小数字,性能差异可以忽略不计。但是第一个变体(即使现在已经优化)随着数量的增加而迅速恶化。尝试:

EXPLAIN ANALYZE SELECT f_array_fill(100,100)
EXPLAIN ANALYZE SELECT f_array_fill_sql(100,100)  -- ~ 50x faster!
于 2013-10-01T23:48:25.713 回答