I want to take an array of n
dimensions and return set containing rows of arrays of n-1
dimensions. For example, take the array ARRAY[[1,2,3], [4,5,6], [7,8,9]]
and return a set {1,2,3}, {4,5,6}, {7,8,9}
. Using unnest returns the set 1,2,3,4,5,6,7,8,9
.
I tried grabbing the unnest function from PostgreSQL 8.4, which seems like it would do what I'm looking for:
CREATE OR REPLACE FUNCTION tstng.unnest2(anyarray)
RETURNS SETOF anyelement
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
RETURN QUERY SELECT $1[i]
FROM generate_series(array_lower($1,1), array_upper($1,1)) i;
END;
$$;
However, SELECT tstng.unnest2(ARRAY[[1,2,3], [4,5,6], [7,8,9]]);
returns the set , ,
(i.e.: 3 null rows).
I've also found that SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[0];
returns null, which I believe to be the root of my problem.
To convert an ARRAY into a set of rows, also known as "flattening," use the UNNEST operator. UNNEST takes an ARRAY and returns a table with a single row for each element in the ARRAY . Because UNNEST destroys the order of the ARRAY elements, you may wish to restore order to the table.
Flattening an array is a process of reducing the dimensionality of an array. In other words, it a process of reducing the number of dimensions of an array to a lower number.
An unnest() will almost certainly scan the array in-order, but once you embed that in a large query the ordering is no longer guaranteed. Quote from this thread: unnest() returns its output in the same order as the input. Since an array is ordered it will be returned in the same output order by unnest.
Common Questions A cross join will take every individual element of your unnested array and join it back to its parent row. This will create multiple rows for each element of your array but you can then filter it down.
To break out 1-dimensional arrays from n-dimensional arrays. (1 <= n < ∞
)
CREATE OR REPLACE FUNCTION unnest_nd_1d(a ANYARRAY, OUT a_1d ANYARRAY)
RETURNS SETOF ANYARRAY
LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
BEGIN
FOREACH a_1d SLICE 1 IN ARRAY a_2d LOOP
RETURN NEXT;
END LOOP;
END
$func$;
PARALLEL SAFE
only for Postgres 9.6 or later.
Later tests revealed this PL/pgSQL function to be fastest.
Related:
CREATE OR REPLACE FUNCTION unnest_2d_1d(anyarray)
RETURNS SETOF anyarray
LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT array_agg($1[d1][d2])
FROM generate_subscripts($1,1) d1
, generate_subscripts($1,2) d2
GROUP BY d1
ORDER BY d1
$func$;
This is an improved and simplified version of the function Lukas posted.
db<>fiddle here
Old sqlfiddle
SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[0]
returns the same as:
SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[17]
... which is NULL
. The manual:
By default, the lower bound index value of an array's dimensions is set to one.
0
has no special meaning as array subscript. There's just nothing there for Postgres arrays with default indexes.
Also, with two-dimensional arrays, you need two indexes to get a base element. Like:
SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[1][2]
Result:
2
The first part of your message is a bit unclear.
SELECT array_dims(ARRAY[[1,2,3], [4,5,6], [7,8,9]]);
Result:
[1:3][1:3]
That's two dimensions with 3 elements (1 to 3) each (9 base elements).
If you want n-1
dimensions then this is a correct result:
SELECT ARRAY (SELECT unnest('{{1,2,3}, {4,5,6}, {7,8,9}}'::int[]))
Result:
{1,2,3,4,5,6,7,8,9}
That's one dimension. unnest()
produces one base element per row (regardless of array dimensions). Your example is just another 2-dimensional array with a missing set of curly brackets ... ?
{1,2,3}, {4,5,6}, {7,8,9}
If you want a slice of the array:
SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[1:2]
Result:
{{1,2,3},{4,5,6}}
Or:
SELECT (ARRAY[[1,2,3], [4,5,6], [7,8,9]])[2:2][1:2]
Result:
{{4,5}}
To flatten the result (get a 1D array):
Read the manual here.
For Postgres versions < 8.4, array_agg()
is not installed by default. Create it first:
CREATE AGGREGATE array_agg(anyelement) (
SFUNC = array_append,
STYPE = anyarray,
INITCOND = '{}'
);
Also, generate_subscripts()
is not born, yet. Use instead:
...
FROM generate_series(array_lower($1,1), array_upper($1,1)) d1
, generate_series(array_lower($1,2), array_upper($1,2)) d2
...
Call:
SELECT unnest_2d_1d(ARRAY[[1,2], [3,4], [5,6]]);
Result
{1,2}
{3,4}
{5,6}
Slices of a multi-dimensional are returned as multi-dimensional arrays. This is a modified version of unnest that will take a 2-dimensional array and return a set of 1-dimensional arrays.
update: modified to use the built-in array_agg aggregate function that was default as of 8.4. (http://www.postgresql.org/docs/9.2/static/functions-aggregate.html)
Caveats:
code:
CREATE OR REPLACE FUNCTION unnest_multidim(anyarray)
RETURNS SETOF anyarray AS
$BODY$
SELECT array_agg($1[series2.i][series2.x]) FROM
(SELECT generate_series(array_lower($1,2),array_upper($1,2)) as x, series1.i
FROM
(SELECT generate_series(array_lower($1,1),array_upper($1,1)) as i) series1
) series2
GROUP BY series2.i
$BODY$
LANGUAGE sql IMMUTABLE;
Result:
select unnest_multidim(array[[1,2,3],[4,5,6],[7,8,9]]);
unnest_multidim
----------------------
{1,2,3}
{4,5,6}
{7,8,9}
(3 rows)
Now, let's say for some reason you want easy access to just one of these arrays that is returned. The following function adds an optional index parameter that will return the nested array of the index you provide, or, if you provide null, will output the full set of "unnested" arrays.
CREATE OR REPLACE FUNCTION unnest_multidim(anyarray, integer)
RETURNS SETOF anyarray AS
$BODY$
SELECT array_agg($1[series2.i][series2.x]) FROM
(SELECT generate_series(array_lower($1,2),array_upper($1,2)) as x, series1.i
FROM
(SELECT CASE WHEN $2 IS NULL THEN
generate_series(array_lower($1,1),array_upper($1,1))
ELSE $2
END as i) series1
) series2
GROUP BY series2.i
$BODY$
LANGUAGE sql IMMUTABLE;
Results:
db=> select unnest_multidim(array[[1,2,3],[4,5,6],[7,8,9]],2);
unnest_multidim
-----------------
{4,5,6}
(1 row)
db=> select unnest_multidim(array[[1,2,3],[4,5,6],[7,8,9]],NULL);
unnest_multidim
-----------------
{1,2,3}
{4,5,6}
{7,8,9}
(3 rows)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With