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

title: Array foreach loop [YSQL] headerTitle: The "array foreach loop" linkTitle: Array foreach loop description: Describes the syntax and semantics of the PL/pgSQL's array foreach loop statements. [YSQL] menu: stable: identifier: array-foreach-loop parent: loop-exit-continue weight: 30 type: docs showRightNav: true One-dimensional arrays are the most commonly usedand the use of even two-dimensional arrays is relatively rare. The use of arrays whose dimensionality is greater than two seems to be rarer still. It's easy to define the semantics of the array foreach loop for a one-dimensional array because the only semantically meaningful spelling of the slice clause for such an array is slice 0. (Notice that the argument of the slice clause must be a non-negative int literal; a negative int literal causes the 42601 syntax error.) Moreover the meaning when the optional slice clause is omitted is defined to be the same as slice 0. Try this: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create function s.f(slice_literal in int) returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare arr constant text[] not null := array['red', 'green', 'blue']; element text[] not null := '{}'; n int not null := 0; begin case slice_literal when 0 then <<"slice-0">>foreach z slice 0 in array arr loop return next; end loop "slice-0"; when 1 then n := 0; <<"slice-1">>foreach element slice 1 in array arr loop n := n + 1; end loop "slice-1"; assert (n = 1) and (element = arr); z := 'Performed just one iteration. The loop variable is set to the entire array.'; return next; when 2 then declare msg text not null := ''; begin <<"slice-2">>foreach element slice 2 in array arr loop null; end loop "slice-2"; exception when arraysubscripterror then get stacked diagnostics msg := message_text; z := 'Caught: "'||msg||'".'; return next; end; end case; end; $body$; ``` Notice that he loop variable for an array foreach loop must be explicitly declared. First invoke it to ask for slice 0: ```plpgsql select s.f(0); ``` This is the result: ```output red green blue ``` This result is self-evidently useful. Next invoke it to ask for slice 1: ```plpgsql select s.f(1); ``` This is the result: ```output Performed just one iteration. The loop variable is set to the entire array. ``` This result shows that slice 1 with a one-dimensional array is pointless. Finally invoke it to ask for slice 2: ```plpgsql select s.f(2); ``` This is the result: ```output Caught: "slice dimension (2) is out of the valid range 0..1" ``` These three results, taken together, show that the only meaningful use of the array foreach loop with a one-dimensional array is most compactly written by omitting the slice clause, thus: ```plpgsql create function s.fsliceomitted() returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare arr constant text[] not null := array['red', 'green', 'blue']; begin <<"slice-0">>foreach z slice 0 in array arr loop return next; end loop "slice-0"; end; $body$; select s.fsliceomitted(); ``` The result is identical to that shown for select s.f(0) shown above. It's easy to define the semantics: The array foreach loop with a one-dimensional array is normally written without the slice clause. The loop variable is set, successively, to each element in the array in storage

A useful demonstration of the semantics of the slice clause requires a multidimensional array. Three dimensions are sufficient. Full generality is demonstrated by specifying values greater than one for each of the three lower bounds. Create the function s.threedimarray() to return such an array: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create function s.threedimarray( zlb in int, zub in int, ylb in int, yub in int, xlb in int, xub in int) returns text[] set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare -- "z" is the major dimension zlen constant int not null := zub - z_lb + 1; ylen constant int not null := yub - y_lb + 1; xlen constant int not null := xub - x_lb + 1; arr text[] not null := arrayfill(null::int, array[zlen, ylen, xlen], array[zlb, ylb, x_lb]); begin assert arraylower(arr, 1) = zlb; assert arraylower(arr, 2) = ylb; assert arraylower(arr, 3) = xlb; assert arrayupper(arr, 1) = zub; assert arrayupper(arr, 2) = yub; assert arrayupper(arr, 3) = xub; -- Populate the array <<overz>>for z in zlb..z_ub loop <<overy>>for y in ylb..y_ub loop <<overx>>for x in xlb..x_ub loop arr[x] := 'z'||z::text||'-y'||y::text||'-x'||x::text; end loop over_x; end loop over_y; end loop over_z; return arr; end; $body$; ``` Notice how the function uses the SQL function to set up an empty array with the required bounds and whose elements all have the same, required, data type. And notice how it uses the and SQL functions to read the array's bounds. The first index, z, in the expression arr\[z\]\[y\]\[x\] has bounds given by the expressions arraylower(arr, 1) and arrayupper(arr, 1). The second index, y, in the expression arr\[z\]\[y\]\[x\] has bounds given by the expressions arraylower(arr, 2) and arrayupper(arr, 2). The third index, x, in the expression arr\[z\]\[y\]\[x\] has bounds given by the expressions arraylower(arr, 3) and arrayupper(arr, 3). Before demonstrating the semantics of the array foreach loop's slice clause, it helps to look at the text literal (i.e. the text typecast) for the value that s.threedimarray() returns for a particular parameterization. Do this: ```plpgsql select s.threedimarray( zlb=>11, zub=>12, ylb=>23, yub=>25, xlb=>36, xub=>39)::text; ``` {{< note title="Array literals." >}} See: the section ; and the subsection in the section in the PostgreSQL documentation. {{< /note >}} The result has no line breaks and so, in a screen window of usual width where line-wrap is turned on, it's very hard to read. Readability is enhanced by adding whitespace manually: line breaks; and a conventional indentation style to line up matching curly braces vertically. The literal starts by representing the loop bounds, using square bracket pairs. The order is from the first dimension through the highest order dimension. Then an = sign separates this from the list of element values. The element values themselves are, by definition, listed in storage order. By definition, too, a run of values for the index that changes fastest, x, is surrounded by a curly-brace pair. Then, a run of such runs is surrounded by a second curly-brace pair when the index that changes next-fastest, y, changes. Finally, the entire list of element values is surrounded by a third curly-brace pair. Use the massaged value, now, as the text SQL

This serves both to ensure that the massage didn't introduce any typos and to present the original array value in yet another way, thus: ```plpgsql select unnest( ' [36:39]= { { {z11-y23-x36,z11-y23-x37,z11-y23-x38,z11-y23-x39}, {z11-y24-x36,z11-y24-x37,z11-y24-x38,z11-y24-x39}, {z11-y25-x36,z11-y25-x37,z11-y25-x38,z11-y25-x39} }, { {z12-y23-x36,z12-y23-x37,z12-y23-x38,z12-y23-x39}, {z12-y24-x36,z12-y24-x37,z12-y24-x38,z12-y24-x39}, {z12-y25-x36,z12-y25-x37,z12-y25-x38,z12-y25-x39} } } '::text[] ); ``` This is the result: ```output z11-y23-x36 z11-y23-x37 z11-y23-x38 z11-y23-x39 z11-y24-x36 z11-y24-x37 z11-y24-x38 z11-y24-x39 z11-y25-x36 z11-y25-x37 z11-y25-x38 z11-y25-x39 z12-y23-x36 z12-y23-x37 z12-y23-x38 z12-y23-x39 z12-y24-x36 z12-y24-x37 z12-y24-x38 z12-y24-x39 z12-y25-x36 z12-y25-x37 z12-y25-x38 z12-y25-x39 ``` You can, of course, produce the same result directly like this: ```plpgsql select unnest(s.threedimarray( zlb=>11, zub=>12, ylb=>23, yub=>25, xlb=>36, xub=>39) ); ``` The unnest() function, by definition, creates a single-column table whose rows are the input array's element values in storage order. Notice how the array's elements are addressed in the s.threedimarray() function: ```output <<overz>>for z in zlb..z_ub loop <<overy>>for y in ylb..y_ub loop <<overx>>for x in xlb..x_ub loop arr[x] := 'z'||z::text||'-y'||y::text||'-x'||x::text; end loop over_x; end loop over_y; end loop over_z; ``` You can picture each element, arr\[z\]\[y\]\[x\], as a cube in a cuboid with the x-axis running from left to right, the y-axis running from bottom to top, and the z-axis running inwards from the x-y originlike the thumb, first finger, and index finger of your left hand, thus: You can use the exact same loop design to display the elements using a table function thus: ```output <<overz>>for z in zlb..z_ub loop <<overy>>for y in ylb..y_ub loop t := ''; <<overx>>for x in xlb..x_ub loop rowmajorordertraversal := rowmajorordertraversal||' '||arr[x]; t := t||arr[x]||' '; end loop over_x; return next; end loop over_y; t := ''; return next; end loop over_z; ``` The complete table function, s.withoutsliceoperator(), is shown below. The innermost loop, overx, concatenates the space-separated element values along the x-axis. The surrounding loop, overy, displays each such concatenation of x values on its own line. And the outermost loop, overz, introduces a bank line between each set of x values, one for each y value, for a particular z value, and the corresponding set for the next z_ value. The nested loops produce this output: ``` z11-y23-x36 z11-y23-x37 z11-y23-x38 z11-y23-x39 z11-y24-x36 z11-y24-x37 z11-y24-x38 z11-y24-x39 z11-y25-x36 z11-y25-x37 z11-y25-x38 z11-y25-x39 z12-y23-x36 z12-y23-x37 z12-y23-x38 z12-y23-x39 z12-y24-x36 z12-y24-x37 z12-y24-x38 z12-y24-x39 z12-y25-x36 z12-y25-x37 z12-y25-x38 z12-y25-x39 ``` This is the same order, the physical storage order, yet again. This traversal order is called . Notice that, for the text literal, the elements are shown in exactly this order. By definition, it simply traverses the elements in order of their physical storage and accesses the associated metadata that specifies the array bounds: The elements along the x-axis are stored contiguously in order of increasing values of x. Each such x-axis run is stored contiguously with the next such run in order of increasing values of y. Each run of x values over all the y values (i.e. each x-y plane) is stored contiguously with the next such plane in order of increasing values of z. Here is the complete definition of the s.withoutsliceoperator() function. Not only does it display the element values from the nested loop, (overx within overy within overz_); it also adds these sanity tests: It concatenates the space-separated element values from the nested loops traversal into the single text value,

It strips off the array bounds representation from the start of the text literal, arrtotexttypecast_; it removes all the curly braces; it removes all the commas; it reduces each run of spaces to just a single space; and it strips off any leading spaces. It concatenates the space-separated element values from the unnest() result as the single text value, unnesttraversal_; and it strips off any leading spaces. It does a foreach traversal of the array and concatenates the space-separated element values that this produces into a single text value foreachtraversal_; and it strips off any leading spaces. Finally, it asserts that all of the text values, rowmajorordertraversal, arrtotexttypecast, unnesttraversal, and foreachtraversal, are identical to each other. Create and execute the table function, s.withoutsliceoperator(), thus: ```plpgsql drop function if exists s.withoutsliceoperator() cascade; create function s.withoutsliceoperator() returns table(t text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare -- z y x arr constant text[] not null := s.threedimarray(11, 12, 23, 25, 36, 39); zlb constant int not null := arraylower(arr, 1); zub constant int not null := arrayupper(arr, 1); ylb constant int not null := arraylower(arr, 2); yub constant int not null := arrayupper(arr, 2); xlb constant int not null := arraylower(arr, 3); xub constant int not null := arrayupper(arr, 3); arrtotext_typecast text not null := arr::text; rowmajororder_traversal text not null := ''; unnest_traversal text not null := ''; foreach_traversal text not null := ''; element text not null := ''; begin <<overz>>for z in zlb..z_ub loop <<overy>>for y in ylb..y_ub loop t := ''; <<overx>>for x in xlb..x_ub loop rowmajorordertraversal := rowmajorordertraversal||' '||arr[x]; t := t||arr[x]||' '; end loop over_x; return next; end loop over_y; t := ''; return next; end loop over_z; declare old_len int not null := -1; -- The array litteral starts with the bounds like this: [36:39]= -- so need a regular expression to represent the general form of this. digits constant text not null := '[0-9]+'; subpattern constant text not null := '\['||digits||':'||digits||'\]'; pattern constant text not null := subpattern||subpattern||subpattern||'='; begin arrtotexttypecast := regexpreplace(arrtotext_typecast, pattern, ''); while (length(arrtotexttypecast) <> oldlen) loop arrtotexttypecast := replace(arrtotexttypecast, '{', ''); arrtotexttypecast := replace(arrtotexttypecast, '}', ''); arrtotexttypecast := replace(arrtotexttypecast, ',', ' '); arrtotexttypecast := replace(arrtotexttypecast, ' ', ' '); oldlen := length(arrtotexttypecast); end loop; end; for element in (select unnest(arr)) loop unnesttraversal := unnesttraversal||' '||element; end loop; foreach element in array arr loop foreachtraversal := foreachtraversal||' '||element; end loop; rowmajorordertraversal := ltrim(rowmajorordertraversal); arrtotexttypecast := ltrim(arrtotexttypecast); unnesttraversal := ltrim(unnesttraversal); foreachtraversal := ltrim(foreachtraversal); assert arrtotexttypecast = rowmajorordertraversal; assert unnesttraversal = rowmajorordertraversal; assert foreachtraversal = rowmajorordertraversal; end; $body$; select s.withoutsliceoperator(); ``` The result has already been shown above. The array foreach loop, when the argument is an array, arr, of any dimensionality and when the slice clause is omitted (or when the slice 0 clause is used): ```plpgsql foreach element in array arr loop ... end loop; ``` simply produces each successive array element, with each successive iteration, in physical storage ordera.k.a. row-major order. This query for loop has exactly the same effect: ```plpgsql for element in (select unnest(arr)) loop ... end loop; ``` The text typecast of an array, arr::text, also lists the element values in physical storage order. Using a three dimensional array as an example, an explicit traversal that uses an overx loop inside an overy loop inside an overz loop. where the elements are addressed using arr\[z\]\[y\]\[x\]_, also lists the element values in physical storage order.