postgresqlplpgsqlpostgresql-9.6database-cursorset-returning-functions

"INSERT INTO ... FETCH ALL FROM ..." can't be compiled


I have some function on PostgreSQL 9.6 returning a cursor (refcursor):

CREATE OR REPLACE FUNCTION public.test_returning_cursor()
  RETURNS refcursor
IMMUTABLE
LANGUAGE plpgsql
AS $$
DECLARE
  _ref refcursor = 'test_returning_cursor_ref1';
BEGIN
  OPEN _ref FOR
  SELECT 'a' :: text AS col1
  UNION
  SELECT 'b'
  UNION
  SELECT 'c';

  RETURN _ref;
END
$$;

I need to write another function in which a temp table is created and all data from this refcursor are inserted to it. But INSERT INTO ... FETCH ALL FROM ... seems to be impossible. Such function can't be compiled:

CREATE OR REPLACE FUNCTION public.test_insert_from_cursor()
  RETURNS table(col1 text)
IMMUTABLE
LANGUAGE plpgsql
AS $$
BEGIN
  CREATE TEMP TABLE _temptable (
    col1 text
  ) ON COMMIT DROP;

  INSERT INTO _temptable (col1)
  FETCH ALL FROM "test_returning_cursor_ref1";

  RETURN QUERY
  SELECT col1
  FROM _temptable;
END
$$;

I know that I can use:

FOR _rec IN
  FETCH ALL FROM "test_returning_cursor_ref1"
LOOP
  INSERT INTO ...
END LOOP;

But is there better way?


Solution

  • Unfortunately, INSERT and SELECT don't have access to cursors as a whole.

    To avoid expensive single-row INSERT, you could have intermediary functions with RETURNS TABLE and return the cursor as table with RETURN QUERY. See:

    CREATE OR REPLACE FUNCTION f_cursor1_to_tbl()
      RETURNS TABLE (col1 text) AS
    $func$
    BEGIN
       -- MOVE BACKWARD ALL FROM test_returning_cursor_ref1;  -- optional, see below
    
       RETURN QUERY
       FETCH ALL FROM test_returning_cursor_ref1;
    END
    $func$  LANGUAGE plpgsql;  -- not IMMUTABLE
    

    Then create the temporary table(s) directly like:

    CREATE TEMP TABLE t1 ON COMMIT DROP
    AS SELECT * FROM f_cursor1_to_tbl();
    

    See:

    Still not very elegant, but much faster than single-row INSERT.

    Note: Since the source is a cursor only the first call succeeds. Executing the function a second time would return an empty set. You would need a cursor with the SCROLL option and move to the start for repeated calls.