8

I am trying to write a loop in a PL/pgSQL function in PostgreSQL 9.3 that returns a table. I used RETURN NEXT; with no parameters after each query in the loop, following examples I found, like:

However, I am still getting an error:

ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.

A minimal code example to reproduce the problem is below. Can anyone please help explain how to fix the test code to return a table?

Minimal example:

CREATE OR REPLACE FUNCTION test0()
  RETURNS TABLE(y integer, result text)
  LANGUAGE plpgsql AS
$func$
DECLARE
    yr RECORD;
BEGIN
    FOR yr IN SELECT * FROM generate_series(1,10,1) AS y_(y) 
    LOOP
        RAISE NOTICE 'Computing %', yr.y;
        SELECT yr.y, 'hi';
        RETURN NEXT;
    END LOOP;
    RETURN;
END
$func$;
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
thor
  • 21,418
  • 31
  • 87
  • 173

3 Answers3

13

The example given may be wholly replaced with RETURN QUERY:

BEGIN
    RETURN QUERY SELECT y_.y, 'hi' FROM generate_series(1,10,1) AS y_(y)
END;

which will be a lot faster.

In general you should avoid iteration wherever possible, and instead favour set-oriented operations.

Where return next over a loop is unavoidable (which is very rare, and mostly confined to when you need exception handling) you must set OUT parameter values or table parameters, then return next without arguments.

In this case your problem is the line SELECT yr.y, 'hi'; which does nothing. You're assuming that the implicit destination of a SELECT is the out parameters, but that's not the case. You'd have to use the out parameters as loop variables like @peterm did, use assignments or use SELECT INTO:

FOR yr IN SELECT * FROM generate_series(1,10,1) AS y_(y) 
LOOP
    RAISE NOTICE 'Computing %', yr.y;
    y := yr.y;
    result := 'hi';
    RETURN NEXT;
END LOOP;
RETURN;
Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
6

What @Craig already explained.

Plus, if you really need a loop, you can have this simpler / cheaper. You don't need to declare an additional record variable and assign repeatedly. Assignments are comparatively expensive in plpgsql. Assign to the OUT variables declared in RETURNS TABLE directly. Those are visible everywhere in the code and the FOR loop can also assign to a list of variables. The manual:

The target is a record variable, row variable, or comma-separated list of scalar variables.

CREATE OR REPLACE FUNCTION test0()
  RETURNS TABLE(y integer, result text)
  LANGUAGE plpgsql AS
$func$
DECLARE
    yr RECORD;  -- now unneeded
BEGIN
   FOR y, result IN
      SELECT g, 'text_'::text || g
      FROM   generate_series(1,10) g
   LOOP
      RAISE NOTICE 'Computing %', y;
      RETURN NEXT;
   END LOOP;
END
$func$;

Additional points

  • Do not use the identifier y twice (as OUT param and column alias) while you can easily avoid it. That's a loaded footgun. If this can't be avoided, table-qualify columns.

  • A final RETURN without params is good form, but totally optional. When control reaches the final END, the complete result is returned automatically.

  • g in FROM generate_series(1,10) g is both table alias and column alias automatically, unless an explicit column alias is given. It is effectively the same as FROM generate_series(1,10) g(g).

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
5

One way to do it

CREATE OR REPLACE FUNCTION test0()
 RETURNS TABLE(y integer, result text) AS $$
BEGIN
    FOR y, result IN 
        SELECT s.y, 'hi' result FROM generate_series(1,10,1) AS s(y)
    LOOP
        RETURN NEXT;
    END LOOP;
END
$$ LANGUAGE plpgsql;

SELECT * FROM test0();

Outcome:

|  Y | RESULT |
|----|--------|
|  1 |     hi |
|  2 |     hi |
|  3 |     hi |
|  4 |     hi |
|  5 |     hi |
|  6 |     hi |
|  7 |     hi |
|  8 |     hi |
|  9 |     hi |
| 10 |     hi |

Here is a SQLFiddle demo

peterm
  • 91,357
  • 15
  • 148
  • 157
  • 2
    Useful, but it'd be nice if you could explain *why* it works, as the original poster is clearly a bit confused about output variables. – Craig Ringer Dec 27 '14 at 06:37