it-swarm.com.de

Geben Sie in PostgreSQL mit PL / pgSQL mehrere Felder als Datensatz zurück

Ich schreibe einen SP mit PL/pgSQL.
Ich möchte einen Datensatz zurückgeben, der Felder aus verschiedenen Tabellen enthält. Könnte ungefähr so ​​aussehen:

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; 

Wie kann ich die Felder aus verschiedenen Tabellen als Felder in einem einzelnen Datensatz zurückgeben?

[Bearbeiten]

Ich habe festgestellt, dass das Beispiel, das ich oben gegeben habe, etwas zu simpel war. Einige der Felder, die ich abrufen muss, werden als separate Zeilen in der abgefragten Datenbanktabelle gespeichert, aber ich möchte sie in der 'abgeflachten' Datensatzstruktur zurückgeben.

Der folgende Code soll zur weiteren Veranschaulichung beitragen:

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
63
skyeagle

Sie müssen einen neuen Typ definieren und Ihre Funktion definieren, um diesen Typ zurückzugeben.

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; 

Wenn Sie mehr als einen Datensatz zurückgeben möchten, müssen Sie die Funktion als returns setof my_type Definieren.


pdate

Eine andere Möglichkeit besteht darin, RETURNS TABLE() zu verwenden, anstatt ein TYPE zu erstellen, das in Postgres 8.4 eingeführt wurde

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

Verwenden Sie nicht CREATE TYPE , um ein polymorphes Ergebnis zurückzugeben. Verwenden und missbrauchen Sie stattdessen RECORD-Typ . Hör zu:

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;

Beachten Sie, dass je nach Eingabe wahlweise zwei oder drei Spalten zurückgegeben werden können.

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)

Dies führt zwar zu Chaos im Code, verwenden Sie also eine konsistente Anzahl von Spalten, aber es ist lächerlich praktisch, wenn Sie optionale Fehlermeldungen mit dem ersten Parameter zurückgeben, der den Erfolg des Vorgangs zurückgibt. Mit einer konsistenten Anzahl von Spalten umgeschrieben:

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;

Fast zur epischen Schärfe:

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)

Aber wie können Sie das in mehrere Zeilen aufteilen, damit der ORM-Layer Ihrer Wahl die Werte in die systemeigenen Datentypen der Sprache Ihrer Wahl konvertieren kann? Die Schärfe:

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)

Dies ist eine der coolsten und am wenigsten genutzten Funktionen in PostgreSQL. Bitte verbreiten Sie das Wort.

104
Sean

Dies kann einfacher mit OUT Parametern sein:

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;
  • Sie brauchen nicht , um einen Typ nur für diese plpgsql-Funktion zu erstellen. Es kann nützlich sein, wenn Sie mehrere Funktionen an denselben Typ binden möchten. Ich benutze es selten mehr, seit OUT Parameter hinzugefügt wurden.

  • Wie Sie vielleicht bemerkt haben, gibt es keine RETURN Anweisung. OUT Parameter werden automatisch zurückgegeben, keine RETURN Anweisung erforderlich.

  • Da OUT -Parameter überall im Funktionskörper sichtbar sind (und wie jede andere Variable verwendet werden können), stellen Sie sicher, dass Sie gleichnamige Spalten tabellenqualifizieren, um Namenskonflikte zu vermeiden.

Noch einfacher - oder mehrere Zeilen zurückgeben

Meistens kann dies weiter vereinfacht werden. Manchmal können Abfragen im Funktionskörper kombiniert werden, was normalerweise (nicht immer) schneller ist. Und Sie können RETURNS TABLE() verwenden - eingeführt mit Postgres 8.4 (lange bevor auch diese Frage gestellt wurde).

Das obige Beispiel könnte folgendermaßen umgeschrieben werden:

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 Ist praktisch dasselbe wie eine Reihe von OUT Parametern in Kombination mit RETURNS record, Nur etwas kürzer/eleganter.

  • Der Hauptunterschied besteht darin, dass diese Funktion 0, 1 oder viele Zeilen zurückgeben kann, während die erste Version immer 1 Zeile zurückgibt.
    Wenn Sie sicherstellen möchten, dass diese Zeile nur 0 oder 1 zurückgibt, fügen Sie wie gezeigt LIMIT 1 Hinzu.

  • RETURN QUERY Ist die sehr praktische und moderne Methode, um Ergebnisse einer Abfrage direkt zurückzugeben.
    Sie können mehrere Instanzen in einer einzigen Funktion verwenden, um der Ausgabe weitere Zeilen hinzuzufügen.

Verschiedene Zeilentypen

Wenn Ihre Funktion dynamisch Ergebnisse mit verschiedenen Zeilentypen zurückgeben soll, lesen Sie hier mehr:

49

Wenn Sie eine Tabelle mit genau diesem Datensatzlayout haben, verwenden Sie den Namen als Typ. Andernfalls müssen Sie den Typ explizit deklarieren:

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; 
5
Quassnoi

Sie können dies erreichen, indem Sie einfach eine Rückgabemenge von Datensätzen mit der Rückgabeabfrage verwenden.

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$

Und nennen Sie diese Funktion als: select * from schemaName.get_two_users_from_school(schoolid) as x(a bigint, b varchar);

2
Ritesh Jha

sie können dies mit dem OUT-Parameter und CROSS JOIN tun

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;

dann benutze es als Tisch:

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

oder

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

oder

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