23

Is it possible to return multiple result sets from a PostgreSQL function, like in MSSQL:

CREATE FUNCTION test
    
AS
    
SELECT * FROM first_table
    
SELECT * FROM second_table

6 Answers 6

22

Return single result set from multiple queries

A simpler way has been around since PostgreSQL 8.3:

CREATE FUNCTION test()
  RETURNS SETOF first_table
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY
   SELECT * FROM first_table;

   RETURN QUERY
   SELECT * FROM second_table;  -- same row type as first_table!
END
$func$;

Call:

SELECT * FROM test();

Both result sets are appended to a single set returned from the function. The row type has to match, of course.
See the manual for RETURN QUERY.

Return multiple cursors

You could return multiple cursors, which is not the same thing, exactly, as you have to FETCH from each cursor in turn. And you have to do it all within the same transaction. There is a code example in the manual. It might be useful to return large row sets from a function. In most cases, I would rather just run each SELECT statement in turn.

Sign up to request clarification or add additional context in comments.

6 Comments

what for different row types? is there any solution? other than cursor. I am looking to get two recordsets on one call.
@UdeetSolanki: Not possible as direct result from a function. There are various ways around it: with cursors, temp tables, document types like json. I suggest you ask a new question, comments are not the place.
Any improvement as of Postgres11?
@MaulikModi: This cannot be "improved" as it's an inherent restriction of the SQL language. Consider asking a new question with details of your case.
@ErwinBrandstetter Other DBMS support returning multiple, hetereogenous, resultsets from a query batch within a procedure: they return 1-distinct-resultset-per-SELECT-statement . It's not a limitation of SQL per-se, but a limitation of Postgres, no?
|
9
CREATE OR REPLACE FUNCTION "pr_GetCustomersAndOrders"()
RETURNS SETOF refcursor AS
$BODY$DECLARE
customerRC refcursor;
orderRC refcursor;
BEGIN
open customerRC FOR
SELECT * FROM customers;
RETURN NEXT customerRC;

open orderRC FOR
SELECT * FROM orders;
RETURN NEXT orderRC;
RETURN;
END;$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION "pr_GetCustomersAndOrders"() OWNER TO postgres;

I.o.w. using refcursors :)

1 Comment

Fitting this to my tables I get: SELECT [* FROM] "pr_GetCustomersAndOrders"(); pr_GetCustomersAndOrders -------------------------- <unnamed portal 11> <unnamed portal 12> Using psql 9.1.
2
CREATE OR REPLACE FUNCTION public.TestReturnMultipleTales
( 
 param_coid integer, 
 ref1 refcursor,
 ref2 refcursor
)
RETURNS SETOF refcursor 
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
ROWS 1000

AS $BODY$
DECLARE
            
BEGIN
  OPEN ref1 FOR SELECT * FROM dbo.tbl1 WHERE coid = param_coid;
  RETURN NEXT ref1;

  OPEN ref2 FOR SELECT * FROM dbo.tbl2 LIMIT 5;
  RETURN NEXT ref2;
END;
$BODY$;

USE IN pgSQL Query:- 

BEGIN;
    SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
    FETCH ALL IN "Ref1";
    FETCH ALL IN "Ref2";
COMMIT;

SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
FETCH ALL IN "Ref1";

SELECT football_players.show_cities_multiple(123456, 'Ref1', 'Ref2');
FETCH ALL IN "Ref2";

Comments

0

If first_table and second_table have the same layout, you can also just use

SELECT * FROM first_table WHERE ...
UNION ALL
SELECT * FROM second_table WHERE ...

[EDIT: Thanks to a commenter (whose name is probably not "null" :) ) for pointing out that UNION ALL is faster than UNION.]

4 Comments

Nitpicking, but UNION ALL would be quicker (there is no "| sort | uniq"), but will return duplicates if there are any.
@null: Good point; updated. (I realise your name probably isn't "null" -- seems a recent SO bug causes this. I was able to fix it by editing the top field on my profile page.)
Yes but what about queries that don't return the same layouts ? It quite limitative if we need to get many sets. I use to have Stored Procedure in SQL Server which returns 10+ result sets. Anything like that in PostgreSQL ?
@MaxiWheat: Frans Bouma's answer (returning REFCURSORs) covers that case, provided you remain within plpgsql. postgresql.org/docs/8.4/interactive/plpgsql-cursors.html explains how cursors can be passed back outside of plpgsql (e.g. to be used with a client library).
0

For example, you can use multiple RETURN NEXT or RETURN QUERY statements in my_func() which returns SETOF RECORD type as shown below:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $$
DECLARE 
  row RECORD;
BEGIN
  FOR row IN VALUES ('John','Smith'), ('David','Miller') LOOP
    RETURN NEXT row; -- Here
  END LOOP;
  FOR row IN VALUES ('Robert','Wilson'), ('Mark','Taylor') LOOP
    RETURN NEXT row; -- Here
  END LOOP;
END;
$$ LANGUAGE plpgsql;

Or:

CREATE FUNCTION my_func() RETURNS SETOF RECORD AS $$
BEGIN
  RETURN QUERY VALUES ('John','Smith'), ('David','Miller'); -- Here
  RETURN QUERY VALUES ('Robert','Wilson'), ('Mark','Taylor'); -- Here
END;
$$ LANGUAGE plpgsql;

*Memos:

  • A RETURN NEXT and RETURN QUERY statement cannot exit a function while a RETURN statement can.

  • You can also use a RETURN NEXT and RETURN QUERY statement together in my_func() as I explain it in my answer:

Then, calling my_func() returns 4 rows as shown below:

postgres=# SELECT * FROM my_func() AS (first_name TEXT, last_name TEXT);
 first_name | last_name
------------+-----------
 John       | Smith
 David      | Miller
 Robert     | Wilson
 Mark       | Taylor
(4 rows)

And, you can use multiple RETURN NEXT or RETURN QUERY statements in my_func() which returns TABLE() type as shown below:

CREATE FUNCTION my_func() RETURNS TABLE(first_name TEXT, last_name TEXT) AS $$
BEGIN
  FOR first_name, last_name IN VALUES ('John','Smith'), ('David','Miller') LOOP
    RETURN NEXT; -- Here
  END LOOP;
  FOR first_name, last_name IN VALUES ('Robert','Wilson'), ('Mark','Taylor') LOOP
    RETURN NEXT; -- Here
  END LOOP;
END;
$$ LANGUAGE plpgsql;

Or:

CREATE FUNCTION my_func() RETURNS TABLE(first_name TEXT, last_name TEXT) AS $$
BEGIN
  RETURN QUERY VALUES ('John','Smith'), ('David','Miller'); -- Here
  RETURN QUERY VALUES ('Robert','Wilson'), ('Mark','Taylor'); -- Here
END;
$$ LANGUAGE plpgsql;

Then, calling my_func() returns 4 rows as shown below:

postgres=# SELECT * FROM my_func();
 first_name | last_name
------------+-----------
 John       | Smith
 David      | Miller
 Robert     | Wilson
 Mark       | Taylor
(4 rows)
postgres=# SELECT my_func();
     my_func
-----------------
 (John,Smith)
 (David,Miller)
 (Robert,Wilson)
 (Mark,Taylor)
(4 rows)

Comments

-1

Yes.

Example:

test=# create function x () returns setof integer language plpgsql as $$ begin return next 1; return next 2; end $$;
CREATE FUNCTION
test=# select * from x();
 x 
---
 1
 2
(2 rows)

You can of course use an existing table/view or a custom type for the returned type.

Example using language SQL:

test=# create table customer (name varchar, birth_date date);
CREATE TABLE
test=# create function y () returns setof customer language sql as $$ 
select * from customer
union all
select * from customer
$$;
CREATE FUNCTION
test=# insert into customer values ('joe', now()::date);
INSERT 0 1
test=# insert into customer values ('jill', now()::date);
INSERT 0 1
test=# select * from y();
 name | birth_date 
------+------------
 joe  | 2009-04-16
 jill | 2009-04-16
 joe  | 2009-04-16
 jill | 2009-04-16
(4 rows)

See here for doc

1 Comment

You only use 1 table, the table "customer". The question is about multiple result sets!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.