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

title: arrayagg(), unnest(), and generatesubscripts() linkTitle: arrayagg(), unnest(), generatesubscripts() headerTitle: arrayagg(), unnest(), and generatesubscripts() description: arrayagg(), unnest(), and generatesubscripts() menu: preview: identifier: array-agg-unnest parent: array-functions-operators type: docs For one-dimensional arrays, but only for these (see ), these two functions have mutually complementary effects in the following sense. After this sequence (the notation is informal): ```output array_agg of "SETOF tuples #1" => "result array" unnest of "result array" => "SETOF tuples #3" ``` The "SETOF tuples #3" has identical shape and content to that of "SETOF tuples #1". And the data type of "result array" is an array of the data type of the tuples. Moreover, and again for the special case of one-dimensional arrays, the function `generate_subscripts()` can be used to produce the same result as `unnest()`. For this reason, the three functions, `arrayagg()`, `unnest()`, and `generatesubscripts()` are described in the same section. This function has two overloads. Purpose: Return a one-dimensional array from a SQL subquery. Its rows might be scalars (that is, the `SELECT` list might be a single column). But, in typical use, they are likely to be of "row" type values. Signature: ```output input value: SETOF anyelement return value: anyarray ``` In normal use, `arrayagg()` is applied to the `SELECT` list from a physical table, or maybe from a view that encapsulates the query. This is shown in the ""_ example below. But first, you can demonstrate the functionality without creating and populating a table by using, instead, the `VALUES` statement. Try this: ```plpgsql values (1::int, 'dog'::text), (2::int, 'cat'::text), (3::int, 'ant'::text); ``` It produces this result: ```output column1 | column2 1 | dog 2 | cat 3 | ant ``` Notice that YSQL has named the `SELECT` list items "column1" and "column2". The result is a so-called `SETOF`. It means a set of rows, just as is produced by a `SELECT` statement. (You'll see the term if you describe the `generateseries()` built-in table function with the `\df` meta-command.) To use the rows that the `VALUES` statement produces as the input for `arrayagg()`, you need to use a named `type`, thus: ```plpgsql create type rt as (f1 int, f2 text); with tab as ( values (1::int, 'dog'::text), (2::int, 'cat'::text), (3::int, 'ant'::text)) select array_agg((column1, column2)::rt order by column1) as arr from tab; ``` It produces this result: ```output arr {"(1,dog)","(2,cat)","(3,ant)"} ``` You recognize this as the text of the literal that represents an array of tuples that are shape-compatible with "type rt". The underlying notions that explain what is seen here are explained in . Recall from that this value doesn't encode the type name. In fact, you could typecast it to any shape compatible type. You can understand the effect of `array_agg()` thus: Treat each row as a "rt[]" array with a single-value. Concatenate (see the ) the values from all the rows in the specified order into a new "rt[]" array. This code illustrates this point: ```plpgsql -- Consider this SELECT: with tab as ( values ((1, 'dog')::rt), ((2, 'cat')::rt), ((3, 'ant')::rt)) select array_agg(column1 order by column1) as arr from tab; -- It can be seen as equivalent this SELECT: select array[(1, 'dog')::rt] || array[(2, 'cat')::rt] || array[(3, 'ant')::rt] as arr; ``` Each of the three

as arr" queries above produces the same result, as was shown after the first of them. This demonstrates their semantic equivalence. To prepare for the demonstration of `unnest()`, save the single-valued result from the most recent of the three queries (but any one of them would do) into a `ysqlsh` variable by using the `\gset` meta-command. This takes a single argument, conventionally spelled with a trailing underscore (for example, "result&#95;") and re-runs the `SELECT` statement that, as the last submitted `ysqlsh` command, is still in the command buffer. (If the `SELECT` doesn't return a single row, then you get a clear error.) In general, when the `SELECT` list has N members, called "c1" through "cN", each of these values is stored in automatically-created variables called "result&#95;c1" through "result&#95;cN". if you aren't already familiar with the `\gset` meta-command, you can read a brief account of how it works in within the major section on `ysqlsh`. Immediately after running the "with... select arrayagg(...) as arr..."_ query above, do this: ```plpgsql \gset result_ \echo :result_arr ``` The `\gset` meta-command is silent. The `\echo` meta-command shows this: ```output {"(1,dog)","(2,cat)","(3,ant)"} ``` The text of the literal is now available for re-use, as was intended. Before considering `unnest()`, look at `array_agg()`'s second overload: Purpose: Return a (N+1)-dimensional array from a SQL subquery whose rows are N-dimensional arrays. The aggregated arrays must all have the same dimensionality. Signature: ```output input value: SETOF anyarray return value: anyarray ``` Here is a positive example: ```plpgsql with tab as ( values ('{a, b, c}'::text[]), ('{d, e, f}'::text[])) select array_agg((column1)::text[] order by column1) as arr from tab; ``` It produces this result: ```output arr {{a,b,c},{d,e,f}} ``` And here is a negative example: ```plpgsql with tab as ( values ('{a, b, c}'::text[]), ('{d, e }'::text[])) select array_agg((column1)::text[] order by column1) as arr from tab; ``` It causes this error: ```output 2202E: cannot accumulate arrays of different dimensionality ``` This function has two overloads. The first is straightforward and has an obvious usefulness. The second is rather exotic. Purpose: Transform the values in a single array into a SQL table (that is, a `SETOF`) these values. Signature: ```output input value: anyarray return value: SETOF anyelement ``` As the sketch at the start of this page indicated, the input to unnest is an array. To use what the code example in the account of arrayagg() set in the `ysqlsh` variable "result&#95;arr" in a SQL statement, you must quote it and typecast it to "rt[]"_. This can be done with the \set meta-command, thus: ```plpgsql \set unnestarg '\'':resultarr'\'::rt[]' \echo :unnest_arg ``` The `\set` meta-command uses the backslash character to escape the single quote character that it also uses to surround the string that it assigns to the target `ysqlsh` variable. The `\echo` meta-command shows this: ```output '{"(1,dog)","(2,cat)","(3,ant)"}'::rt[] ``` Now use it as the actual argument for `unnest()` thus: ```plpgsql with rows as ( select unnest(:unnest_arg) as rec) select (rec).f1, (rec).f2 from rows order by 1; ``` The parentheses around the column alias "rec" are required to remove what the SQL compiler would otherwise see as an ambiguity, and would report as a "42P01 undefinedtable"_ error. This is the result: ```output f1 | f2 +-- 1 | dog 2 | cat 3 | ant ``` As promised, the original `SETOF` tuples has been

Purpose: Transform the values in a variadic list of arrays into a SQL table whose columns each are a `SETOF` the corresponding input array's values. This overload can be used only in the `FROM` clause of a subquery. Each input array might have a different type and a different cardinality. The input array with the greatest cardinality determines the number of output rows. The rows of those input arrays that have smaller cardinalities are filled at the end with `NULL`s. The optional `WITH ORDINALITY` clause adds a column that numbers the rows. Signature: ```output input value: <variadic list of> anyarray return value: many coordinated columns of SETOF anyelement ``` ```plpgsql create type rt as (a int, b text); \pset null '<is null>' select * from unnest( array[1, 2], array[10, 20, 30, 45, 50], array['a', 'b', 'c', 'd'], array[(1, 'p')::rt, (2, 'q')::rt, (3, 'r')::rt, (4, 's')::rt] ) with ordinality as result(arr1, arr2, arr3, arr4a, arr4n, n); ``` It produces this result: ```output arr1 | arr2 | arr3 | arr4a | arr4n | n --++--+--+--+ 1 | 10 | a | 1 | p | 1 2 | 20 | b | 2 | q | 2 <is null> | 30 | c | 3 | r | 3 <is null> | 45 | d | 4 | s | 4 <is null> | 50 | <is null> | <is null> | <is null> | 5 ``` Start by aggregating three `int[]` array instances and by preparing the result as an `int[]` literal for the next step using the same `\gset` technique that was used above: ```plpgsql with tab as ( values ('{1, 2, 3}'::int[]), ('{4, 5, 6}'::int[]), ('{7, 8, 9}'::int[])) select array_agg(column1 order by column1) as arr from tab \gset result_ \set unnestarg '\'':resultarr'\'::int[]' \echo :unnest_arg ``` Notice that the SQL statement, this time, is not terminated with a semicolon. Rather, the `\gset` meta-command acts as the terminator. This makes the `ysqlsh` output less noisy. This is the result: ```output '{{1,2,3},{4,5,6},{7,8,9}}'::int[] ``` You recognize this as the literal for a two-dimensional array. Now use this as the actual argument for `unnest()`: ```plpgsql select unnest(:unnest_arg) as val order by 1; ``` It produces this result: ```output val -- 1 2 3 4 5 6 7 8 9 ``` This `SETOF` result lists all of the input array's "leaf" values in row-major order. This term is explained in ) within the "Functions for reporting the geometric properties of an array" section. Notice that, for the multidimensional case, the original input to `arrayagg()` was not_, therefore, regained. This point is emphasized by aggregating the result: ```plpgsql with a as (select unnest(:unnest_arg) as val) select array_agg(val order by val) from a; ``` It produces this result: ```output array_agg {1,2,3,4,5,6,7,8,9} ``` You started with a two-dimensional array. But now you have a one-dimensional array with the same values as the input array in the same row-major order. This result has the same semantic content that the `arraytostring()` function produces: ```plpgsql select arraytostring(:unnest_arg, ','); ``` It produces this result: ```output arraytostring 1,2,3,4,5,6,7,8,9 ``` See . This shows how you can use the `FOREACH` loop in procedural code, with an appropriate value for the `SLICE` operand, to unnest an array into a set of subarrays whose dimensionality you can

At one end of the range, you can mimmic `unnest()` and produce scalar values. At the other end of the range, you can produce a set of arrays with dimensionality `n - 1` where `n` is the dimensionality of the input array. The basic illustration of the functionality of `array_agg()` showed how it can convert the entire contents of a table (or, by extension, the `SETOF` rows defined by a `SELECT` execution) into a single array value. This can be useful to return a large `SELECT` result in its entirety (in other words, in a single round trip) to a client program. Another use is to populate a single newly-created "masterswithdetails" table from the fully projected and unrestricted `INNER JOIN` of a classic "masters" and "details" pair of tables. The new table has all the columns that the source "masters" table has and all of its rows. And it has an additional "details" column that holds, for each "masters" row, a "detailst[]" array that represents all of the child rows that it has in the source "details" table. The type "details&#95;t" has all of the columns of the "details" table except the "details.masterspk" foreign key column. This column vanishes because, as the join column, it vanishes in the `INNER JOIN`. The "details" table's "payload" is now held in place in a single multivalued field in the new "masters&#95;with&#95;details" table. Start by creating and populating the "masters" and "details" tables: ```plpgsql create table masters( master_pk int primary key, master_name text not null); insert into masters(masterpk, mastername) values (1, 'John'), (2, 'Mary'), (3, 'Joze'); create table details( master_pk int not null, seq int not null, detail_name text not null, constraint detailspk primary key(masterpk, seq), constraint masterpkfk foreign key(master_pk) references masters(master_pk) match full on delete cascade on update restrict); insert into details(masterpk, seq, detailname) values (1, 1, 'cat'), (1, 2, 'dog'), (2, 1, 'rabbit'), (2, 2, 'hare'), (2, 3, 'squirrel'), (2, 4, 'horse'), (3, 1, 'swan'), (3, 2, 'duck'), (3, 3, 'turkey'); ``` Next, create a view that encodes the fully projected, unrestricted inner join of the original data, and inspect the result set that it represents: ```plpgsql create or replace view original_data as select master_pk, m.master_name, d.seq, d.detail_name from masters m inner join details d using (master_pk); select master_pk, master_name, seq, detail_name from original_data order by master_pk, seq; ``` This is the result: ```output masterpk | mastername | seq | detail_name --+-+--+- 1 | John | 1 | cat 1 | John | 2 | dog 2 | Mary | 1 | rabbit 2 | Mary | 2 | hare 2 | Mary | 3 | squirrel 2 | Mary | 4 | horse 3 | Joze | 1 | swan 3 | Joze | 2 | duck 3 | Joze | 3 | turkey ``` Next, create the type "details&#95;t" and the new table: ```plpgsql create type detailst as (seq int, detailname text); create table masterswithdetails ( master_pk int primary key, master_name text not null, details details_t[] not null); ``` Notice that you made the "details" column `not null`. This was a choice. It adds semantics that are notoriously difficult to capture in the original two table design without tricky, and therefore error-prone, programming of triggers and the like. You have implemented the so-called "mandatory one-to-many"

In the present example, the rule says (in the context of the entity-relationship model that specifies the requirements) that an occurrence of a "Master" entity type cannot exist unless it has at least one, but possibly many, child occurrences of a "Detail" entity type. Next, populate the new table and inspect its contents: ```plpgsql insert into masterswithdetails select master_pk, master_name, arrayagg((seq, detailname)::details_t order by seq) as agg from original_data group by masterpk, mastername; select masterpk, mastername, details from masterswithdetails order by 1; ``` This is the result: ```output masterpk | mastername | details --+-+ 1 | John | {"(1,cat)","(2,dog)"} 2 | Mary | {"(1,rabbit)","(2,hare)","(3,squirrel)","(4,horse)"} 3 | Joze | {"(1,swan)","(2,duck)","(3,turkey)"} ``` Here's a helper function to show the primitive values that the "details&#95;t[]" array encodes without the clutter of the array literal syntax: ```plpgsql create function prettydetails(arr in detailst[]) returns text language plpgsql as $body$ declare arrtype constant text := pgtypeof(arr); ndims constant int := array_ndims(arr); lb constant int := array_lower(arr, 1); ub constant int := array_upper(arr, 1); begin assert arrtype = 'detailst[]', 'assert failed: ndims = %', arr_type; assert ndims = 1, 'assert failed: ndims = %', ndims; declare line text not null := rpad(arr[lb].seq::text||': '||arr[lb].detail_name::text, 12)|| ' | '; begin for j in (lb + 1)..ub loop line := line|| rpad(arr[j].seq::text||': '||arr[j].detail_name::text, 12)|| ' | '; end loop; return line; end; end; $body$; ``` Notice that this is not a general purpose function. Rather, it expects that the input is a _"details&#95;t; ; and . Invoke it like this: ```plpgsql select masterpk, mastername, pretty_details(details) from masterswithdetails order by 1; ``` It produces this result: ```output masterpk | mastername | pretty_details --+-+-- 1 | John | 1: cat | 2: dog | 2 | Mary | 1: rabbit | 2: hare | 3: squirrel | 4: horse | 3 | Joze | 1: swan | 2: duck | 3: turkey | ``` Next, create a view that uses `unnest()` to re-create the effect of the fully projected, unrestricted inner join of the original data, and inspect the result set that it represents: ```plpgsql create or replace view new_data as with v as ( select master_pk, master_name, unnest(details) as details from masterswithdetails) select master_pk, master_name, (details).seq, (details).detail_name from v; select master_pk, master_name, seq, detail_name from new_data order by master_pk, seq; ``` The result is identical to what the "original&#95;data" view represents. But rather than relying on visual inspection, can check that the "new&#95;data" view and the "original&#95;data" view represent the identical result by using SQL thus: ```plpgsql with originalexceptnew as ( select masterpk, mastername, seq, detail_name from original_data except select masterpk, mastername, seq, detail_name from new_data), newexceptoriginal as ( select masterpk, mastername, seq, detail_name from new_data except select masterpk, mastername, seq, detail_name from original_data), originalexceptnewunionnewexceptoriginal as ( select masterpk, mastername, seq, detail_name from originalexceptnew union select masterpk, mastername, seq, detail_name from newexceptoriginal) select case count(*) when 0 then '"newdata" is identical to "originaldata."' else '"newdata" differs from "originaldata".' end as result from originalexceptnewunionnewexceptoriginal; ``` This is the result: ```output result "newdata" is identical to "originaldata." ``` Notice that if you choose the "masters&#95;with&#95;details" approach (either as a migration from a two-table approach in an extant application, or as an initial choice in a new application) you must appreciate the

Prerequisite: You must be confident that the "details" rows are genuinely private each to its own master and do not implement a many-to-many relationship in the way that the "order&#95;items" table does between the "customers" table and the "items" table in the classic sales order entry model that is frequently used to teach table design according to the relational model. Pros: You can enforce the mandatory one-to-many requirement declaratively and effortlessly. Changing and querying the data will be faster because you use single table, single-row access rather than two-table, multi-row access. You can trivially recapture the query functionality of the two-table approach by implementing a "new&#95;data" unnesting view as has been shown. So you can still find, for example, rows in the "masters&#95;with&#95;details" table where the "details" array has the specified values like this: ```plpgsql with v as ( select masterpk, mastername, seq, detail_name from new_data where detail_name in ('rabbit', 'horse', 'duck', 'turkey')) select master_pk, master_name, arrayagg((seq, detailname)::details_t order by seq) as agg from v group by masterpk, mastername order by 1; ``` This is the result: ```output masterpk | mastername | agg --+-+- 2 | Mary | {"(1,rabbit)","(4,horse)"} 3 | Joze | {"(2,duck)","(3,turkey)"} ``` Cons: Changing the data in the "details" array is rather difficult. Try this (in the two-table regime): ```plpgsql update details set detail_name = 'bobcat' where master_pk = 2 and detail_name = 'squirrel'; select master_pk, master_name, seq, detail_name from original_data where master_pk = 2 order by master_pk, seq; ``` This is the result: ```output masterpk | mastername | seq | detail_name --+-+--+- 2 | Mary | 1 | rabbit 2 | Mary | 2 | hare 2 | Mary | 3 | bobcat 2 | Mary | 4 | horse ``` Here's how you achieve the same effect, and check that it worked as intended, in the new regime. Notice that you need to know the value of "seq" for the "rt" object that has the "detail&#95;name" value of interest. This can be done by implementing a dedicated PL/pgSQL function that encapsulates `array_replace()` or that replaces a value directly by addressing it using its index. But it's hard to do without that. (These methods are described in .) ```plpgsql update masterswithdetails set details = array_replace(details, '(3,squirrel)', '(3,bobcat)') where master_pk = 2; select master_pk, master_name, seq, detail_name from new_data where master_pk = 2 order by master_pk, seq; ``` The result is identical to the result shown for querying "original&#95;data" above. Implementing the requirement that the values of "detail&#95;name" must be unique for a given "masters" row is trivial in the old regime: ```plpgsql create unique index on details(masterpk, detailname); ``` To achieve the effect in the new regime, you'd need to write a PL/pgSQL function, with return type `boolean` that scans the values in the "details" array and returns `TRUE` when there are no duplicates among the values of the "detail&#95;name" field and that otherwise returns `FALSE`. Then you'd use this function as the basis for a check constraint in the definition of the "details&#95;with&#95;masters" table. This is a straightforward programming task, but it does take more effort than the declarative implementation of the business rule that the two-table regime allows. Purpose: Return the index values, along the specified dimension, of an array as a SQL table (that is, a `SETOF`) these `int`

Signature: ```output input value: anyarray, integer, boolean return value: SETOF integer ``` The second input parameter specifies the dimension along which the index values should be generated. The third, optional, input parameter controls the ordering of the values. The default value `FALSE` means generate the index values in ascending order from the lower index bound to the upper index bound; and the value `TRUE` means generate the index values in descending order from the upper index bound to the lower index bound. It's useful to use the same array in each of several examples. Make it available thus: ```plpgsql drop function if exists arr() cascade; create function arr() returns int[] language sql as $body$ select array[17, 42, 53, 67]::int[]; $body$; ``` Now demonstrate the basic behavior generatesubscripts():_ ```plpgsql select generate_subscripts(arr(), 1) as subscripts; ``` This is the result: ```output subscripts 1 2 3 4 ``` Asks for the subscripts to be generated in reverse order. ```plpgsql select generate_subscripts(arr(), 1, true) as subscripts; ``` This is the result: ```output subscripts 4 3 2 1 ``` `generateseries()` can be use to produce the same result as `generatesubscripts()`. Notice that `generateseries()` doesn't have a "reverse"_ option. This means that, especially when you want the results in reverse order, the syntax is significantly more cumbersome, as this example shows: ```plpgsql select arrayupper(arr(), 1) + 1 - generateseries( array_lower(arr(), 1), array_upper(arr(), 1) ) as subscripts; ``` The following example creates a procedure that compares the results of `generatesubscripts()` and `generateseries()`, when the latter is invoked in a way that will produce the same results as the former. The procedure's input parameter lets you specify along which dimension you want to generate the index values. To emphasize how much easier it is to write the `generate_subscripts()` invocation, the test uses the reverse index order option. The array is constructed using the array literal notation (see ) that explicitly sets the lower index bound along each of the array's three dimensions. is used to aggregate the results from each approach so that they can be compared simply by using the . ```plpgsql create or replace procedure p(dim in int) language plpgsql as $body$ declare arr constant int[] not null := ' [7:10]={ { { 1, 2, 3, 4},{ 5, 6, 7, 8},{ 9,10,11,12} }, { {13,14,15,16},{17,18,19,20},{21,22,23,24} } }'::int[]; subscripts_1 constant int[] := ( with v as ( select generate_subscripts(arr, dim) as s) select array_agg(s) from v ); lb constant int := array_lower(arr, dim); ub constant int := array_upper(arr, dim); subscripts_2 constant int[] := ( with v as ( select generate_series(lb, ub) as s) select array_agg(s) from v ); begin assert subscripts1 = subscripts2, 'assert failed'; end; $body$; do $body$ begin call p(1); call p(2); call p(3); end; $body$; ``` Each of the calls finishes silently, showing that the asserts hold. Both of the built-ins, `generateseries()` and `generatesubscripts()` are table functions. For this reason, they are amenable to this aliasing locution: ```plpgsql select mytablealias.mycolumnalias from generateseries(1, 3) as mytablealias(mycolumn_alias); ``` This is the result: ```output mycolumnalias -- 1 2 3 ``` The convention among PostgreSQL users is to use `g(i)` with these two built-ins, where "g" stands for "generate" and "i" is the common favorite for a loop iterand in procedural programming. You are very likely, therefore, to see something like this: ```plpgsql select

from generate_subscripts('[5:7]={17, 42, 53}'::int[], 1) as g(i); ``` with this result: ```output i 5 6 7 ``` This is useful because without the locution, the result of each of these table functions is anonymous. The more verbose alternative is to define the aliases in a `WITH` clause, as was done above: ```plpgsql with g(i) as ( select generate_subscripts('[5:7]={17, 42, 53}'::int[], 1)) select g.i from g; ``` The most obvious use is to tabulate the array values along side of the index values, using the immediately preceding example: ```plpgsql drop table if exists t cascade; create table t(k text primary key, arr int[]); insert into t(k, arr) values ('Array One', '{17, 42, 53, 67}'), ('Array Two', '[5:7]={19, 47, 59}'); select i, (select arr from t where k = 'Array One')[i] from generate_subscripts((select arr from t where k = 'Array One'), 1) as g(i); ``` It produces this result: ```output i | arr +-- 1 | 17 2 | 42 3 | 53 4 | 67 ``` Notice that this: ```output (select arr from t where k = 1)[i] ``` has the same effect as this: ```output (select arr[i] from t where k = 1) ``` It was written the first way to emphasize the annoying textual repetition of "(select arr from t where k = 1)". This highlights a critical difference between SQL and a procedural language like PL/pgSQL. The latter allows you so initialize a variable with an arbitrarily complex and verbose expression and then just to use the variable's name thereafter. But SQL has no such notion. Notice that the table t has two rows. You can't generalize the SQL shown immediately above to list the indexes with their array values for both rows. This is where the cross join lateral syntax comes to the rescue: ```plpgsql with c(k, a, idx) as ( select k, arr, indexes.idx from t cross join lateral generate_subscripts(t.arr, 1) as indexes(idx)) select k, idx, a[idx] from c order by k; ``` It produces this result: ```output k | idx | a --+--+- Array One | 1 | 17 Array One | 2 | 42 Array One | 3 | 53 Array One | 4 | 67 Array Two | 5 | 19 Array Two | 6 | 47 Array Two | 7 | 59 ``` Here is the PL/pgSQL re-write. ```plpgsql do $body$ <<b>>declare arr constant int[] := (select arr from t where k = 'Array Two'); i int; begin for b.i in ( select g.i from generate_subscripts(arr, 1) as g(i)) loop raise info '% | % ', i, arr[i]; end loop; end b; $body$; ``` The result (after manually stripping the "INFO:" prompts), is the same as the SQL approach that uses `generatesubscripts()` with cross join lateral_, shown above, produces: ```output 5 | 19 6 | 47 7 | 59 ``` Notice that having made the transition to a procedural approach, there is no longer any need to use `generate_subscripts()`. Rather, and can be used in the ordinary way to set the bounds of the integer variant of a `FOR` loop: ```plpgsql do $body$ declare arr constant int[] := (select arr from t where k = 1); begin for i in reverse arrayupper(arr,

1) loop raise info '% | % ', i, arr[i]; end loop; end; $body$; ``` It produces the same result. Try these two examples: ```plpgsql with v as ( select array[17, 42, 53]::int[] as arr) select (select arr[idx] from v) as val from generate_subscripts((select arr from v), 1) as subscripts(idx); ``` and: ```plpgsql with v as ( select array[17, 42, 53]::int[] as arr) select unnest((select arr from v)) as val; ``` Each uses the same array, "array[1, 2, 3]::int[]", and each produces the same result, thus: ```output val -- 17 42 53 ``` One-dimensional arrays are by far the most common use of the array data type. This is probably because a one-dimensional array of "row" type values naturally models a schema-level tablealbeit that an array brings an unavoidable ordering of elements while the rows in a schema-level table have no intrinsic order. In the same way, an array of scalar elements models the values in a column of a schema-level table. Certainly, almost all the array examples in the use one-dimensional arrays. Further, it is common to want to present an array's elements as a `SETOF` these values. For this use case, and as the two code examples above show, `unnest()` is simpler to use than `generatesubscripts()`. It is far less common to care about the actual dense sequence of index values that address an array's elementsfor which purpose you would need `generatesubscripts()`. Moreover, `unnest()` (as has already been shown in this section) "flattens" an array of any dimensionality into the sequence of its elements in row-major order but `generate_subscripts()` brings no intrinsic functionality to do this. You can certainly achieve the result, as these two examples show for a two-dimensional array. Compare this: ```plpgsql select unnest('{{17, 42, 53},{57, 67, 73}}'::int[]) as element; ``` with this: ```plpgsql with a as ( select '{{17, 42, 53},{57, 67, 73}}'::int[] as arr), s1 as ( select generate_subscripts((select arr from a), 1) as i), s2 as ( select generate_subscripts((select arr from a), 2) as j) select (select arr from a) element from s1,s2 order by s1.i, s2.j; ``` Again, each uses the same array (this time '{{17, 42, 53},{57, 67, 73}}'::int[]) and each produces the same result, thus: ```output element 17 42 53 57 67 73 ``` You could generalize this approach for an array of any dimensionality. However, the `generatesubscripts()` approach is more verbose, and therefore more error-prone, than the `unnest()` approach. However, because "order by s1.i, s2.j"_ makes your ordering rule explicit, you could define any ordering that suited your purpose. See . The `FOREACH` loop brings dedicated syntax for looping over the contents of an array. The loop construct uses the `SLICE` keyword to specify the subset of the array's elements over which you want to iterate. Typically you specify that the iterand is an array with fewer dimensions than the array over which you iterate. Because this functionality is intrinsic to the `FOREACH` loop, and because it would be very hard to write the SQL statement that produces this kind of slicing, you should use the `FOREACH` loop when you have this kind of requirement. If you want to consume the output in a surrounding SQL statement, you can use `FOREACH` in a PL/pgSQL table function that returns a `SETOF` the sub-array that you need. You specify the `RETURNS` clause of such a table function using the `TABLE` keyword.