使用PL / pgSQL将多个字段作为PostgreSQL中的记录返回

时间:2010-12-28 16:47:54

标签: sql postgresql stored-procedures types plpgsql

我正在使用PL / pgSQL编写SP 我想返回一个记录,由几个不同的表中的字段组成。看起来像这样:

CREATE OR REPLACE FUNCTION get_object_fields(name text)
  RETURNS RECORD AS $$
BEGIN
  -- fetch fields f1, f2 and f3 from table t1
  -- fetch fields f4, f5 from table t2
  -- fetch fields f6, f7 and f8 from table t3
  -- return fields f1 ... f8 as a record
END
$$ language plpgsql; 

如何将不同表格中的字段作为单个记录中的字段返回?

[编辑]

我已经意识到我上面给出的例子有点过于简单了。我需要检索的一些字段将被保存为要查询的数据库表中的单独行,但我想在“展平”记录结构中返回它们。

下面的代码应该有助于进一步说明:

CREATE TABLE user (id int, school_id int, name varchar(32));

CREATE TYPE my_type (
  user1_id   int,
  user1_name varchar(32),
  user2_id   int,
  user2_name varchar(32)
);

CREATE OR REPLACE FUNCTION get_two_users_from_school(schoolid int)
  RETURNS my_type AS $$
DECLARE
  result my_type;
  temp_result user;
BEGIN
  -- for purpose of this question assume 2 rows returned
  SELECT id, name INTO temp_result FROM user where school_id = schoolid LIMIT 2;
  -- Will the (pseudo)code below work?:
  result.user1_id := temp_result[0].id ;
  result.user1_name := temp_result[0].name ;
  result.user2_id := temp_result[1].id ;
  result.user2_name := temp_result[1].name ;
  return result ;
END
$$ language plpgsql

6 个答案:

答案 0 :(得分:102)

不要使用CREATE TYPE来返回多态结果。请改为使用和滥用RECORD type。看看:

CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE 
  ret RECORD;
BEGIN
  -- Arbitrary expression to change the first parameter
  IF LENGTH(a) < LENGTH(b) THEN
      SELECT TRUE, a || b, 'a shorter than b' INTO ret;
  ELSE
      SELECT FALSE, b || a INTO ret;
  END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;

请注意,根据输入,它可以选择返回两个三个列。

test=> SELECT test_ret('foo','barbaz');
             test_ret             
----------------------------------
 (t,foobarbaz,"a shorter than b")
(1 row)

test=> SELECT test_ret('barbaz','foo');
             test_ret             
----------------------------------
 (f,foobarbaz)
(1 row)

这确实会对代码造成严重破坏,因此请使用一致数量的列,但使用第一个返回操作成功的参数返回可选错误消息非常方便。使用一致数量的列重写:

CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE 
  ret RECORD;
BEGIN
  -- Note the CASTING being done for the 2nd and 3rd elements of the RECORD
  IF LENGTH(a) < LENGTH(b) THEN
      ret := (TRUE, (a || b)::TEXT, 'a shorter than b'::TEXT);
  ELSE
      ret := (FALSE, (b || a)::TEXT, NULL::TEXT);
   END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;

几乎是史诗般的热情:

test=> SELECT test_ret('foobar','bar');
   test_ret    
----------------
 (f,barfoobar,)
(1 row)

test=> SELECT test_ret('foo','barbaz');
             test_ret             
----------------------------------
 (t,foobarbaz,"a shorter than b")
(1 row)

但是如何将其拆分为多行,以便您选择的ORM层可以将值转换为您选择的本机数据类型的语言?热情:

test=> SELECT a, b, c FROM test_ret('foo','barbaz') AS (a BOOL, b TEXT, c TEXT);
 a |     b     |        c         
---+-----------+------------------
 t | foobarbaz | a shorter than b
(1 row)

test=> SELECT a, b, c FROM test_ret('foobar','bar') AS (a BOOL, b TEXT, c TEXT);
 a |     b     | c 
---+-----------+---
 f | barfoobar | 
(1 row)

这是PostgreSQL中最酷最少使用的功能之一。请传播这个词。

答案 1 :(得分:53)

您需要定义一个新类型并定义函数以返回该类型。

CREATE TYPE my_type AS (f1 varchar(10), f2 varchar(10) /* , ... */ );

CREATE OR REPLACE FUNCTION get_object_fields(name text) 
RETURNS my_type 
AS 
$$

DECLARE
  result_record my_type;

BEGIN
  SELECT f1, f2, f3
  INTO result_record.f1, result_record.f2, result_record.f3
  FROM table1
  WHERE pk_col = 42;

  SELECT f3 
  INTO result_record.f3
  FROM table2
  WHERE pk_col = 24;

  RETURN result_record;

END
$$ LANGUAGE plpgsql; 

如果要返回多个记录,则需要将该函数定义为returns setof my_type


更新

另一种选择是使用RETURNS TABLE()而不是创建Postgres 8.4中引入的TYPE

CREATE OR REPLACE FUNCTION get_object_fields(name text) 
  RETURNS TABLE (f1 varchar(10), f2 varchar(10) /* , ... */ )
...

答案 2 :(得分:48)

这可以更简单OUT parameters

CREATE OR REPLACE FUNCTION get_object_fields(
          name text
    ,OUT user1_id   int
    ,OUT user1_name varchar(32)
    ,OUT user2_id   int
    ,OUT user2_name varchar(32)
) AS 
$func$
BEGIN
    SELECT t.user1_id, t.user1_name
    INTO     user1_id,   user1_name
    FROM   tbl1 t
    WHERE  t.tbl1_id = 42;

    user2_id := user1_id + 43; -- some calculation

    SELECT t.user2_name
    INTO     user2_name
    FROM   tbl2 t
    WHERE  t.tbl2_i = user2_id;
END
$func$ LANGUAGE plpgsql;
  • 您不需要 来创建一个类型,只是为了这个plpgsql函数。如果要将几个函数绑定到同一类型,可能非常有用。由于添加了OUT参数,我很少再使用它。

  • 您可能已经注意到,没有RETURN声明。 OUT参数会自动返回,不需要RETURN语句。

  • 由于OUT参数在函数体内的任何地方都可见(并且可以像任何其他变量一样使用),因此请确保对同名的列进行表限定以避免命名冲突。

更简单 - 或返回多行

大多数情况下,这可以进一步简化。有时可以组合函数体中的查询,这通常(并非总是)更快。并且您可以使用RETURNS TABLE() - 在Postgres 8.4中引入(在此问题被提出之前很久)。

上面的例子可以改写为:

CREATE OR REPLACE FUNCTION get_object_fields(name text)
  RETURNS TABLE (
     user1_id   int
    ,user1_name varchar(32)
    ,user2_id   int
    ,user2_name varchar(32)) AS 
$func$
BEGIN
    RETURN QUERY
    SELECT t1.user1_id, t1.user1_name, t2.user2_id, t2.user2_name
    FROM   tbl1 t1
    JOIN   tbl2 t2 ON t2.user2_id = t1.user1_id + 43
    WHERE  t1.tbl1_id = 42
    LIMIT  1;  -- may be optional
END
$func$ LANGUAGE plpgsql; 
  • RETURNS TABLE实际上与将一堆OUT参数与RETURNS record结合使用相同,只是更短/更优雅。

  • 主要区别在于,此函数可以返回0,1行或多行,而第一个版本总是返回1行。
    如果你想确定,这个只返回0或1行,添加LIMIT 1就像演示一样。

  • RETURN QUERY是直接从查询返回结果的非常方便的现代方法 您可以在单个函数中使用多个实例来向输出添加更多行。

各种行类型

如果您的函数应该动态返回具有不同行类型的结果,具体取决于输入,请在此处阅读更多内容:

答案 3 :(得分:5)

如果您有一个具有此确切记录布局的表,请将其名称用作类型,否则您必须明确声明该类型:

CREATE OR REPLACE FUNCTION get_object_fields
        (
        name text
        )
RETURNS mytable
AS
$$
        DECLARE f1 INT;
        DECLARE f2 INT;
        …
        DECLARE f8 INT;
        DECLARE retval mytable;
        BEGIN
        -- fetch fields f1, f2 and f3 from table t1
        -- fetch fields f4, f5 from table t2
        -- fetch fields f6, f7 and f8 from table t3
                retval := (f1, f2, …, f8);
                RETURN retval;
        END
$$ language plpgsql; 

答案 4 :(得分:2)

您可以通过简单地使用返回查询作为记录的返回集来实现此目的。

创建或替换功能schemaName.get_two_users_from_school(schoolid bigint)      返回SETOF记录      LANGUAGE plpgsql     AS $函数$     开始

 return query
  SELECT id, name FROM schemaName.user where school_id = schoolid;

end;
$function$

并将此函数称为:select * from schemaName.get_two_users_from_school(schoolid) as x(a bigint, b varchar);

答案 5 :(得分:0)

你可以使用OUT参数和CROSS JOIN

来做到这一点
CREATE OR REPLACE FUNCTION get_object_fields(my_name text, OUT f1 text, OUT f2 text)
AS $$
SELECT t1.name, t2.name
FROM  table1 t1 
CROSS JOIN table2 t2 
WHERE t1.name = my_name AND t2.name = my_name;
$$ LANGUAGE SQL;

然后将其用作表格:

select get_object_fields( 'Pending') ;
get_object_fields
-------------------
(Pending,code)
(1 row)

select * from get_object_fields( 'Pending');
f1    |   f
---------+---------
Pending | code
(1 row)

select (get_object_fields( 'Pending')).f1;
f1
---------
Pending
(1 row)