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


71

我正在使用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 AS (
  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

是否调查过返回多条记录,例如returns setof my_type
nate c

@nate:它不是返回集合的函数。我需要在SP内提取记录,然后在函数返回的数据中从已检索集中的个人记录中检索字段-听起来比实际要复杂得多-请参见上面的代码
skyeagle 2010年

Answers:


64

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

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()而不是创建TYPEPostgres 8.4中引入的

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

请参阅修改后的问题-原来的问题对于我的实际需求而言过于简化。谢谢
skyeagle 2010年

我不确定我是否理解“新”问题,但是所有这些归结为找到可检索所需结果的正确查询。有了它们后,返回结果应该没问题
a_horse_with_no_name

新的问题是,我需要能够偏移到检索到的行中并从检索到的集合中获取特定字段,以便可以填充从SP返回的“扁平化”结构。新问题归结为以下两个问题:(1)。如何使用哪种数据类型从查询中接收一组行(2)>如何访问返回的第N行中的字段“ f1”?
skyeagle 2010年

我建议为此提出一个新问题。简而言之:对于1),您将使用游标遍历结果并选择所需的结果;对于2),您将保留一个计数器,例如使用row_number()来标识特定行。甚至更好:仅选择行。但是,所有这些都不会更改函数的签名。返回指定的类型就足够了。
a_horse_with_no_name

不要使用新TYPE的解决此问题。只需使用RECORD和别名成员即可RECORD。请参阅stackoverflow.com/questions/4547672/…以获取不包含customTYPE的更正确答案。
肖恩

115

不要使用CREATE TYPE返回多态结果。请改用和滥用RECORD类型。看看这个:

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中最酷,使用最多的功能之一。请散布这个词。


确实方便。我想知道如何再次分割此记录...谢谢!
alfonx 2012年

@Sean:非常好的回答,谢谢-我将尝试这个。在最后一个示例中拆分结果非常酷。
SabreWolfy 2012年

@Sean:谢谢。我觉得SELECT a, b, c ...非常有用。我怎么能使用该方法的东西,如:SELECT code, theFunction(code) from theTable ;,我在哪里SELECT从荷兰国际集团的表,而不是功能?
SabreWolfy 2012年

5
@Sean:1.从Postgres 9.2开始,纯SQL语句也被重新计划。详细信息在这里。2.返回类型独立EXECUTE或不独立。3.答案中的技术仅适用于单行结果,不适用于SETOF record。4.如果返回类型仍然是常量(例如您的示例),请使用众所周知的类型。带有匿名记录的想法仅在少数情况下具有不同的返回类型才有意义。
Erwin Brandstetter 2014年

1
如果查询的结果为0,会发生什么情况?该函数仍将返回一行(ret RECORD),即使它预期返回0行。
Yaki Klein

53

返回单行

OUT参数更简单:

CREATE OR REPLACE FUNCTION get_object_fields(_school_id int
                                       , OUT user1_id   int
                                       , OUT user1_name varchar(32)
                                       , OUT user2_id   int
                                       , OUT user2_name varchar(32)) AS 
$func$
BEGIN
   SELECT INTO user1_id, user1_name
          u.id, u.name
   FROM   users u
   WHERE  u.school_id = _school_id
   LIMIT  1;  -- make sure query returns 1 row - better in a more deterministic way?

   user2_id := user1_id + 1; -- some calculation

   SELECT INTO user2_name
          u.name       
   FROM   users u
   WHERE  u.id = user2_id;
END
$func$  LANGUAGE plpgsql;

呼叫:

SELECT * FROM get_object_fields(1);
  • 你并不需要,只是这种PLPGSQL功能的缘故创建一个类型。如果要将多个功能绑定到同一个复合类型,这可能会很有用。否则,OUT参数就可以了。

  • 没有RETURN声明。OUT参数以这种形式自动返回,仅返回一行。RETURN是可选的。

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

更简单-还返回0-n行

通常,如果可以合并函数主体中的查询,则此操作可以更简单,更快。你可以使用RETURNS TABLE()(自从Postgres 8.4起,很早就问了这个问题)返回0-n行。

上面的示例可以写成:

CREATE OR REPLACE FUNCTION get_object_fields2(_school_id int)
  RETURNS TABLE (user1_id   int
               , user1_name varchar(32)
               , user2_id   int
               , user2_name varchar(32)) AS 
$func$
BEGIN
   RETURN QUERY
   SELECT u1.id, u1.name, u2.id, u2.name
   FROM   users u1
   JOIN   users u2 ON u2.id = u1.id + 1
   WHERE  u1.school_id = _school_id
   LIMIT  1;  -- may be optional
END
$func$  LANGUAGE plpgsql;

呼叫:

SELECT * FROM get_object_fields2(1);
  • RETURNS TABLE与将一堆OUT参数与组合在一起实际上RETURNS SETOF record只是更短。

  • 主要区别在于:该函数可以返回0、1或许多行,而第一个版本始终返回1行。如所示
    添加LIMIT 1仅允许0或1行。

  • RETURN QUERY直接从查询返回结果的简单方法。
    您可以在单个函数中使用多个实例,以将更多行添加到输出中。

db <>在这里摆弄(演示两个)

可变行类型

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


the function does not return a "COMPOSITE" type • the function does not return a SETOF table当与Hasura一起尝试时,我得到了。
marcellothearcane

1
@marcellothearcane:该功能按公告工作。参见添加的小提琴
Erwin Brandstetter

谢谢,这是Hasura的问题,我想-我要回来了SETOF <table>,这似乎有效!
marcellothearcane

6

如果您的表具有正确的记录布局,请使用其名称作为类型,否则必须显式声明该类型:

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; 

3

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

CREATE OR REPLACE FUNCTION schemaName.get_two_users_from_school(schoolid bigint)
 RETURNS SETOF record
 LANGUAGE plpgsql
AS $function$
begin

 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);


1

您可以使用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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.