The "integer for loop"

Here's a minimal example that shows the use of all the optional syntax elements:

\c :db :u
drop schema if exists s cascade;
create schema s;

create function s.f()
  returns table(z text)
  set search_path = pg_catalog, pg_temp
  language plpgsql
as $body$
begin
  <<"Loop-1">>for j in reverse 60..5 by 13 loop
    z := j::text; return next;
  end loop "Loop-1";
end;
$body$;

select s.f();

This is the result:

 60
 47
 34
 21
 8

A name that requires double-quoting was used as the label simply to emphasize that this choice is, of course, legal.

Notice that the loop variable, j, isn't explicitly declared. The code works because the defined semantics of this kind of loop include the implicit declaration of the loop variable as an integer (and, surprisingly, not as a bigint).

The semantics can be understood unambiguously be re-writing it as an infinite loop, thus:

declare
  j int not null := 60;
begin
  <<"Loop-1">>loop
    exit when j < 5;
    z := j::text; return next;
    j := j - 13;
  end loop "Loop-1";
end;

This rewrite makes it clear that, if j had been earlier declared in an enclosing block statement's declaration section, then the implicitly declared loop variable, j, would simply shield it from the earlier-declared i in accordance with the normal rules for nested block statements.

You can rewrite an example of any of the four other kinds of loop (the while loop, the integer for loop, the array foreach loop, and the query for loop) as an infinite loop. And sometimes mutual rewrite is possible between other pairs of kinds of loop statement. For example, an array foreach loop can be re-written as an integer for loop over the array's index values. The present example could have been re-written as a while loop (while j >= 5). The infinite loop is the most flexible kind—and therefore you cannot always rewrite this kind of loop as another kind (unless you use an artificial device that adds no value—like while true loop).