Project: YugabyteDB (App Definition and Development / Database)
File: query-for-loop.md

title: Query for loop [YSQL] headerTitle: The "query for loop" linkTitle: Query for loop description: Describes the syntax and semantics of the "query for loop" [YSQL] menu: stable: identifier: query-for-loop parent: loop-exit-continue weight: 40 type: docs showRightNav: true The query for loop has three variants according to how the query is defined: with a subquery with a so-called bound ref cursor variable with a dynamic SQL statement Do this: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create table s.t(k serial primary key, v int not null); insert into s.t(v) select generate_series(0, 24, 5); create function s.f(klo in int, khi in int) returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare rec record; begin for rec in ( select k, v from s.t order by k) loop z := rpad(rec.k::text, 10)||rec.v::text; return next; end loop; z := ''; return next; for rec in ( select v from s.t where k between klo and khi order by k) loop z := rec.v::text; return next; end loop; end; $body$; ``` Test it thus: ```plpgsql select s.f(2, 4); ``` This is the result: ```plpgsql 1 0 2 5 3 10 4 15 5 20 5 10 15 ``` Look at the first query for loop: ```plpgsql for rec in ( select k, v from s.t order by k) loop z := rpad(rec.k::text, 10)||rec.v::text; return next; end loop; ``` Notice these points: The loop variable for a query for loop must be explicitly declared. The loop variable, rec, has the data type record. This is a so-called pseudo-type. While you can use it as the data type of a subprogram's formal argument or of a PL/pgSQL variable, you cannot use it as the data type of, for example, a table's column. The attempt "create table s.x(k serial primary key, v record)" causes the 42P16 error: ```output column "v" has pseudo-type record ``` The loop variable, rec, can therefore accommodate, at runtime, whatever shape the loop's defining query happens to establishi.e. it is polymorphic. The polymorphism is emphasized by the second query for loop: ```plpgsql for rec in ( select v from s.t where k between klo and khi order by k) loop z := rec.v::text; return next; end loop; ``` The select list in the first loop is k, v. But in the second loop, it's just v alone. In most cases, you'll simply use ordinary variables or formal arguments (i.e. not a record) for the loop variable(s) like this: ```plpgsql create function s.g(vlo in int, vhi in int) returns table(kout int, vout int) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ begin for kout, vout in ( with c as (update s.t set v = v + 3 where k between vlo and vhi returning k, v) select k, v from c order by k ) loop return next; end loop; end; $body$; select kout, vout from s.g(3, 7); ``` This is the result: ```output kout | vout -+- 3 | 13 4 | 18 5 | 23 ``` This example makes another

The query for loop cannot use an insert... returning, an update... returning, or a delete... returning statement directly. (The attempt causes a syntax error.) However, the rules of top-level SQL do allow a select statement to use a common table expression (a.k.a. CTE) that is defined with such a DML statement that uses a returning clause. See the section . You can therefore take advantage of this to use a query for loop to iterate over the result set that the returning clause of a DML statement defines. See the subsection on the page. Try this: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create table s.t(k serial primary key, v int not null); insert into s.t(v) select generate_series(0, 49, 5); create function s.f() returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare cur cursor(klo int, khi int) for ( select k, v from s.t where k between klo and khi order by k); begin assert pg_typeof(cur)::text = 'refcursor'; for rec in cur(2, 4) loop assert pg_typeof(rec)::text = 'record'; z := rpad(rec.k::text, 10)||rec.v::text; return next; end loop; z := ''; return next; for rec in cur(7, 9) loop z := rpad(rec.k::text, 10)||rec.v::text; return next; end loop; end; $body$; select s.f(); ``` This is the result: ```output 2 5 3 10 4 15 7 30 8 35 9 40 ``` Notice these points: With this form of the query for loop, the loop variable is an implicitly declared record. (The first assert confirms this.) You cannot use, say, two ordinarily declared int variables, like kout and vout, even if this would serve your purposes better. The cursor is implicitly opened just before the loop's first iteration. And it's implicitly closed when the loop exits. The parameterization of the bound cursor variable does not allow the use of, say, (klo in int, khi in int). The in/out mode can only be inand this is simply implied. Even though the spelling of the declaration of the bound refcursor variable, cur, seems to declare it as plain cursor, the second assert shows that the data type of cur is in fact refcursor. The declaration syntax is simply a historical quirk. Finally, try this: ```plpgsql create function s.g() returns refcursor set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare cur cursor for ( select k, v from s.t where k between 2 and 4 order by k); begin return cur; end; $body$; create function s.h() returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare cur refcursor := s.g(); begin assert pg_typeof(cur)::text = 'refcursor'; /* for rec in cur loop end loop; */ end; $body$; select s.h(); ``` The functions are created without error. And s.h(), as written, executes without error. Of course, it's useless because it does nothing. Try to re-create s.h() with the query for loop

The attempt causes the 42601 syntax error with this message: ```output cursor FOR loop must use a bound cursor variable ``` The status as bound of a variable whose data type is refcursor is established by the special syntax in the declaration section that's used in the function s.g(): ```plpgsql declare cur cursor for ( select k, v from s.t where k between 2 and 4 order by k); ``` But this status is retained only within the block statement that the declaration section introduces. The data type of the return value of s.g() is simply an ordinary unbound refcursorand, as such, this declaration in s.h(): ```plpgsql cur refcursor := s.g(); ``` has the identical effect to just this: ```plpgsql cur refcursor; ``` This means that a query for loop with a bound refcursor variable brings very limited added value beyond what a query for loop with a subquery brings. The function s.f(), above, shows what you might find to be useful added value: the same bound refcursor variable is used, but with different parameterizations, twice within the same block statement whose declaration section defines the cursor. Try this: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create table s.t(k serial primary key, v int not null); insert into s.t(v) select generate_series(0, 49, 5); create function s.f(klo in int, khi in int) returns table(k int, v int) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare qry constant text not null := ' select k, v from s.t where k between $1 and $2 order by k;'; begin for k, v in execute qry using klo, khi loop return next; end loop; end; $body$; select k, v from s.f(5, 8); ``` This is the result: ```output k | v +- 5 | 20 6 | 25 7 | 30 8 | 35 ``` You might think that it could help readability by surrounding the dynamically defined query with parentheses like this: ```plpgsql for k, v in (execute qry using klo, khi) loop ``` But the attempt causes the 42601 syntax error and complains about the opening parenthesis. Finally, try this ```plpgsql create function s.g(klo in int, khi in int) returns table(k int, v int) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare stmt constant text not null := ' update s.t set v = v + 3 where k between $1 and $2 returning k, v;'; begin for k, v in execute stmt using klo, khi loop return next; end loop; end; $body$; select k, v from s.g(5, 8); ``` This is the result: ```output k | v +- 5 | 23 6 | 28 7 | 33 8 | 38 ``` In other words, when the loop's SQL statement is defined dynamically, you can use an insert... returning statement directly in the loop's definition. (The same holds for update... returning and delete... returning.) Notice, though, that the syntax of such a DML statement with a returning clause does not allow an order by clause. You might, therefore, want to use the same construct where the DML statement is defined within a CTE for a query for loop with a dynamic SQL statement that was shown in the example for a query for loop with a subquery, above.