簡體   English   中英

使用 PL/pgSQL 在 PostgreSQL 中返回多個字段作為記錄

[英]Return multiple fields as a record in PostgreSQL with PL/pgSQL

我正在編寫一個 SP,使用 PL/pgSQL。
我想返回一個記錄,由幾個不同表中的字段組成。 可能看起來像這樣:

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

不要使用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 中最酷和最未被充分利用的功能之一。 請廣而告之。

您需要定義一個新類型並定義您的函數以返回該類型。

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) /* , ... */ )
...

返回單行

使用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<> 在這里小提琴(展示兩者)

不同的行類型

如果您的函數應該根據輸入動態返回具有不同行類型的結果,請在此處閱讀更多信息:

如果您有一個具有這種精確記錄布局的表,請將其名稱用作類型,否則您必須明確聲明該類型:

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; 

您可以通過簡單地使用返回查詢作為返回記錄集來實現這一點。

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

您可以使用 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)
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 設置為任意整數。 函數輸入參數是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$;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM