【问题标题】:How to pass multiple rows to PostgreSQL function?如何将多行传递给 PostgreSQL 函数?
【发布时间】:2017-05-05 17:54:21
【问题描述】:

我们如何传递一个(无限数量的)行数组(即一个常量表)作为 PostgreSQL 函数的参数/参数?

这是一个想法:

CREATE TYPE foo AS (
    x bigint,
    y smallint,
    z varchar(64)
);

CREATE OR REPLACE FUNCTION bar(bigint, foo[]) RETURNS TABLE(a bigint, x bigint, y smallint, z varchar(64)) AS
$$
    SELECT $1, x, y, z FROM unnest($2);
$$
LANGUAGE SQL;

下面的函数调用有效,但有没有办法让它更短?

SELECT * FROM bar(1, ARRAY[(1,2,'body1'),(2,1,'body2')]::foo[]);

例如,我们不能删除 ::foo[] 演员表,但是有没有办法重写一些东西以便我们可以省略它?

我们应该使用可变参数吗?

【问题讨论】:

    标签: postgresql function variadic-functions


    【解决方案1】:

    我的谷歌搜索一直把我带到这里,所以我将发布一个可能与 OP 的需求不完全匹配的答案,但可能对看到标题的其他人有所帮助如何将多行传递给PostgreSQL 函数?

    OP 的原始请求是针对一个类型的:

    CREATE TYPE foo AS (
        x bigint,
        y smallint,
        z varchar(64)
    );
    

    如果您像我一样,您可能希望将标准 SELECT 查询的结果传递给函数。所以想象一下我有一个表(而不是一个类型)创建为:

    CREATE TABLE foo AS (
        x bigint,
        y smallint,
        z varchar(64)
    );
    

    我想将结果传递给函数:

    SELECT * from foo WHERE x = 12345;
    

    结果可能是零行或多行。

    根据https://www.postgresql.org/docs/9.5/static/rowtypes.html 的 postgres 文档,创建表也会导致创建具有相同名称的复合类型。这很有帮助,因为这会自动处理原始问题中的 CREATE TYPE foo,我现在可以将其作为数组传递给函数。

    现在我可以创建一个接受 foo 类型值数组的函数(简化为关注传入的内容以及记录的使用方式,而不是返回的内容):

    CREATE OR REPLACE FUNCTION bar(someint bigint, foos foo[]) RETURNS ...
    LANGUAGE plpgsql
    AS $$
    DECLARE
        foo_record record;
    begin
    
    -- We are going to loop through each composite type value in the array
    -- The elements of the composite value are referenced just like 
    -- the columns in the original table row
    FOREACH foo_record IN ARRAY foos LOOP
      -- do something, maybe like:
      INSERT INTO new_foo (
        x, y, z
      )
      VALUES (
        foo_record.x,
        foo_record.y,
        foo_record.z
      );
    
    END LOOP;
    
    RETURN...
    END;
    $$;
    

    这个函数bar(bigint, foo[]) 可以很简单地调用:

    SELECT bar(4126521, ARRAY(SELECT * from foo WHERE x = 12345));
    

    foo 表上查询的所有行作为 foo 类型数组传递。我们看到的函数然后对这些行中的每一行执行一些操作。

    尽管该示例是人为设计的,并且可能与 OP 所要求的不完全一样,但它符合问题的标题,并且可能使其他人不必进行更多搜索以找到他们需要的内容。

    编辑命名函数参数使事情变得更容易

    【讨论】:

    • 失败并出现以下错误:错误:子查询必须只返回一列第 2 行:SELECT bar(4126521, ARRAY(SELECT * from foo WHERE x = 12345)); ^ SQL 状态:42601 字符:22
    【解决方案2】:

    PostgreSQL 还没有表值变量,所以没有什么是漂亮的。传递数组效率低下,但适用于合理大小的输入。

    对于更大的输入,通常有效的方法是传递一个 refcursor。这很笨拙,但对于较大的数据集很实用,有时与临时表结合使用。

    例如

    CREATE OR REPLACE FUNCTION bar(i bigint, c refcursor) RETURNS TABLE(a bigint, x bigint, y smallint, z varchar(64)) AS
    $$
    DECLARE
        cursrow foo;
    BEGIN
        LOOP
            FETCH NEXT FROM c INTO cursrow;
            a := i;
            x := cursrow.x;
            y := cursrow.y;
            z := cursrow.z;
            RETURN NEXT;
            IF NOT FOUND THEN
                EXIT;
            END IF;
        END LOOP;
        RETURN;
    END;
    $$;
    

    用法:

    demo=> BEGIN;
    BEGIN
    demo=> DECLARE "curs1" CURSOR FOR VALUES (1,2,'body1'), (2,1,'body2');
    DECLARE CURSOR
    craig=> SELECT bar(1, 'curs1');
          bar      
    ---------------
     (1,1,2,body1)
     (1,2,1,body2)
     (1,,,)
    (3 rows)
    
    demo=> COMMIT;
    COMMIT
    

    不漂亮。但是,plpgsql 永远不会。遗憾的是它没有行值左值,因为能够编写像 (x, y, z) := cursrowROW(x, y, z) := cursrow 这样的东西会让它不那么难看。

    RETURN NEXT 有效,但前提是您返回 record 未命名参数或 TABLE

    很遗憾,你不能在子表达式中使用 SQL(不是 plpgsql)FETCH ALL,所以你不能写

    RETURN QUERY NEXT i, cursrow.* FROM (FETCH ALL FROM c) AS cursrow;
    

    【讨论】:

      【解决方案3】:

      似乎问题之一是使用了smallint 类型,它不能从int 常量隐式转换。并考虑以下几点:

      -- drop function if exists bar(bigint, variadic foo[]);
      -- drop type if exists foo;
      
      CREATE TYPE foo AS (
          x bigint,
          y int, -- change type to integer
          z varchar(64)
      );
      
      CREATE OR REPLACE FUNCTION bar(bigint, variadic foo[]) RETURNS TABLE(
        a bigint,
        x bigint,
        y int, -- and here
        z varchar(64)) AS
      $$
          SELECT $1, x, y, z FROM unnest($2);
      $$
      LANGUAGE SQL;
      
      -- Voila! It is even simpler then the using of the ARRAY constructor
      SELECT * FROM bar(1, (1,2,'body1'), (2,1,'body2'), (3,4,'taddy bear'));
      

      dbfiddle

      About variadic parameters

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-15
        • 2019-06-22
        • 1970-01-01
        • 1970-01-01
        • 2013-09-13
        • 2019-04-01
        • 2013-06-12
        • 1970-01-01
        相关资源
        最近更新 更多