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

前端 未结 6 1370
生来不讨喜
生来不讨喜 2020-11-27 11:57

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 O         


        
6条回答
  •  情歌与酒
    2020-11-27 12:34

    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.

提交回复
热议问题