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

发布于 2024-10-09 03:28:18 字数 1276 浏览 5 评论 0原文

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

I am writing a SP, using PL/pgSQL.
I want to return a record, comprised of fields from several different tables. Could look something like this:

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; 

How may I return the fields from different tables as fields in a single record?

[Edit]

I have realized that the example I gave above was slightly too simplistic. Some of the fields I need to be retrieving, will be saved as separate rows in the database table being queried, but I want to return them in the 'flattened' record structure.

The code below should help illustrate further:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

陪我终i 2024-10-16 03:28:18

不要使用 CREATE 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 中最酷且最未被充分利用的功能之一。请传播出去。

Don't use CREATE TYPE to return a polymorphic result. Use and abuse the RECORD type instead. Check it out:

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;

Pay attention to the fact that it can optionally return two or three columns depending on the input.

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)

This does wreak havoc on code, so do use a consistent number of columns, but it's ridiculously handy for returning optional error messages with the first parameter returning the success of the operation. Rewritten using a consistent number of columns:

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;

Almost to epic hotness:

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)

But how do you split that out in to multiple rows so that your ORM layer of choice can convert the values in to your language of choice's native data types? The hotness:

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)

This is one of the coolest and most underused features in PostgreSQL. Please spread the word.

三生路 2024-10-16 03:28:18

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

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


Update

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

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

You need to define a new type and define your function to return that type.

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; 

If you want to return more than one record you need to define the function as returns setof my_type


Update

Another option is to use RETURNS TABLE() instead of creating a TYPE which was introduced in Postgres 8.4

CREATE OR REPLACE FUNCTION get_object_fields(name text) 
  RETURNS TABLE (f1 varchar(10), f2 varchar(10) /* , ... */ )
...
最佳男配角 2024-10-16 03:28:18

返回单行

使用 OUT< 更简单/strong> 参数

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 记录结合< /code>,只是更短。

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

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

db<>fiddle 此处(演示两者)

变化row-type

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

To return a single row

Simpler with OUT parameters:

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;

Call:

SELECT * FROM get_object_fields(1);
  • You don't need to create a type just for the sake of this plpgsql function. It may be useful if you want to bind multiple functions to the same composite type. Else, OUT parameters do the job.

  • There is no RETURN statement. OUT parameters are returned automatically with this form that returns a single row. RETURN is optional.

  • Since OUT parameters are visible everywhere inside the function body (and can be used just like any other variable), make sure to table-qualify columns of the same name to avoid naming conflicts! (Better yet, use distinct names to begin with.)

Simpler yet - also to return 0-n rows

Typically, this can be simpler and faster if queries in the function body can be combined. And you can use RETURNS TABLE() (since Postgres 8.4, long before the question was asked) to return 0-n rows.

The example from above can be written as:

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;

Call:

SELECT * FROM get_object_fields2(1);
  • RETURNS TABLE is effectively the same as having a bunch of OUT parameters combined with RETURNS SETOF record, just shorter.

  • The major difference: this function can return 0, 1 or many rows, while the first version always returns 1 row.
    Add LIMIT 1 like demonstrated to only allow 0 or 1 row.

  • RETURN QUERY is simple way to return results from a query directly.
    You can use multiple instances in a single function to add more rows to the output.

db<>fiddle here (demonstrating both)

Varying row-type

If your function is supposed to dynamically return results with a different row-type depending on the input, read more here:

感情废物 2024-10-16 03:28:18

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

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; 

If you have a table with this exact record layout, use its name as a type, otherwise you will have to declare the type explicitly:

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; 
你的往事 2024-10-16 03:28:18

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

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

You can achieve this by using simply as a returns set of records using return query.

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$

And call this function as : select * from schemaName.get_two_users_from_school(schoolid) as x(a bigint, b varchar);

殤城〤 2024-10-16 03:28:18

您可以使用 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)

you can do this using OUT parameter and 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;

then use it as a table:

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

or

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

or

select (get_object_fields( 'Pending')).f1;
f1
---------
Pending
(1 row)
好久不见√ 2024-10-16 03:28:18
CREATE TABLE users(user_id int, school_id int, name text);
insert into users values (1, 10,'alice')
,(5, 10,'boy')
,(13, 10,'cassey')
,(17, 10,'delores')
,(4, 11,'elaine');

我将 user_id 设置为任意 int。函数输入参数是school_id。因此,如果 school_id 是 10,您希望得到以下结果:

 user_id | name  | user_id | name
---------+-------+---------+------
       1 | alice |       5 | boy

因此您的查询应该类似于:

with a as (
select u1.user_id,
     u1.name from  users u1 
        where school_id = 10 order by user_id limit 1),
b as      
(select u2.user_id,u2.name from users u2 
        where school_id = 10  order by user_id limit 1 offset 1 )
select * from a  cross JOIN b ;

那么让我们将查询包装到 plpgsql 函数中。

CREATE OR REPLACE FUNCTION 
    get_object_fields2(_school_id int)
  RETURNS TABLE (user1_id   int
               , user1_name text
               , user2_id   int
               , user2_name text) 
               LANGUAGE plpgsql AS 
$func$
DECLARE countu integer;
BEGIN
    countu := (
        select count(*) from users where school_id = _school_id);
    IF countu >= 2 THEN
        RETURN QUERY
            with a as (
            select u1.user_id,
                u1.name from  users u1 
                where school_id = _school_id 
                    order by user_id limit 1),
            b as(
                select u2.user_id,u2.name from users u2 
                where school_id = _school_id 
                    order by user_id limit 1 offset 1 )
            select * from a  cross JOIN b;
    elseif countu = 1 then
    return query
      select u1.user_id, u1.name,u1.user_id, u1.name
        from  users u1 where school_id = _school_id; 
    else 
        RAISE EXCEPTION 'not found';
    end if;
END
$func$;
CREATE TABLE users(user_id int, school_id int, name text);
insert into users values (1, 10,'alice')
,(5, 10,'boy')
,(13, 10,'cassey')
,(17, 10,'delores')
,(4, 11,'elaine');

I setted the user_id as arbitrary int. The function input parameter is the school_id. So if the school_id is 10 you hope to get the following result:

 user_id | name  | user_id | name
---------+-------+---------+------
       1 | alice |       5 | boy

So your query should be something like:

with a as (
select u1.user_id,
     u1.name from  users u1 
        where school_id = 10 order by user_id limit 1),
b as      
(select u2.user_id,u2.name from users u2 
        where school_id = 10  order by user_id limit 1 offset 1 )
select * from a  cross JOIN b ;

So let's wrap the query to the plpgsql function.

CREATE OR REPLACE FUNCTION 
    get_object_fields2(_school_id int)
  RETURNS TABLE (user1_id   int
               , user1_name text
               , user2_id   int
               , user2_name text) 
               LANGUAGE plpgsql AS 
$func$
DECLARE countu integer;
BEGIN
    countu := (
        select count(*) from users where school_id = _school_id);
    IF countu >= 2 THEN
        RETURN QUERY
            with a as (
            select u1.user_id,
                u1.name from  users u1 
                where school_id = _school_id 
                    order by user_id limit 1),
            b as(
                select u2.user_id,u2.name from users u2 
                where school_id = _school_id 
                    order by user_id limit 1 offset 1 )
            select * from a  cross JOIN b;
    elseif countu = 1 then
    return query
      select u1.user_id, u1.name,u1.user_id, u1.name
        from  users u1 where school_id = _school_id; 
    else 
        RAISE EXCEPTION 'not found';
    end if;
END
$func$;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文