Can't aggregate arrays - arrays

I can create an array of arrays:
select array[array[1, 2], array[3, 4]];
array
---------------
{{1,2},{3,4}}
But I can't aggregated arrays:
select array_agg(array[c1, c2])
from (
values (1, 2), (3, 4)
) s(c1, c2);
ERROR: could not find array type for data type integer[]
What am I missing?

I use:
CREATE AGGREGATE array_agg_mult(anyarray) (
SFUNC = array_cat,
STYPE = anyarray,
INITCOND = '{}'
);
and queries like:
SELECT array_agg_mult( ARRAY[[x,x]] ) FROM generate_series(1,10) x;
Note that you must aggregate 2-dimensional arrays, so you'll often want to wrap an input array in a single-element ARRAY[array_to_aggregate] array constructor.

Related

Declaring Arrays of Arrays in Bigquery

I'm creating a bigquery procedure to collect data from an array through a function that contains the Array
This is the function:
CREATE OR REPLACE FUNCTION MY_DATASET.MY_FUNCTION() AS (
(
select [
['DATA1','DATA1PARAM1'],
['DATA2','DATA2PARAM2']
] as arrayTest
)
);
Cannot construct array with element type ARRAY<STRING> because nested arrays are not supported
This is the procedure, where I declare the variable and set it:
CREATE OR REPLACE PROCEDURE MY_DATASET.MY_PROCEDURE()
BEGIN
DECLARE tablesArray ARRAY<STRING>;
SET tablesArray = MY_DATASET.MY_FUNCTION();
END;
Cannot coerce expression BILLING_COMPARE.arrayTablesCartografiaToCompare() to type ARRAY<STRING>
How can I declare an Array of Arrays or what is the best way to do this?
An array of an array is not allowed. However, an array of a struct with an array is possible:
With test AS (
(
select [
struct(['DATA1','DATA1PARAM1'] as data),
struct(['DATA2','DATA2PARAM2'])
] as arrayTest
)
)
SELECT * from test, unnest(arrayTest) with offset

What is the equivalent of postgresSQL unnest() in snowflake sql

How do i modify the PostgresSQL in snowflake?
UNNEST(array[
'x' || to_char(date_trunc('MONTH', max(date)), 'Mon YYYY' ,
'y' || to_char(date_trunc('MONTH', max(date)), 'Mon YYYY')
)])
You can use "flatten" to break out values from the array, and then "table" to convert the values into a table:
-- Use an array for testing:
select array_construct(1, 2, 3, 4, 5);
-- Flattens into a table with metadata for each row:
select * from table(flatten(input => array_construct(1, 2, 3, 4, 5)));
--Pulls out just the values from the array:
select value::integer from table(flatten(input => array_construct(1, 2, 3, 4, 5)));
The "::integer" part casts the values to the data type you want from the array. It's optional but recommended.
You can approximate the syntax of unnest by creating a user defined table function:
create or replace function UNNEST(V array)
returns table ("VALUE" variant)
language SQL
aS
$$
select VALUE from table(flatten(input => V))
$$;
You would call it like this:
select * from table(unnest(array_construct(1, 2, 3, 4, 5)));
This returns a table with a single column named VALUE of type variant. You can make a version that returns strings, integers, etc.

Moving PostgreSQL bigint array unique value to another index

How can I move the array bigint value from one index to another? For example, I have an array ARRAY[1, 2, 3, 4] of bigint unique values and want to move value 1 to index 3 so the final result would be ARRAY[2, 3, 1, 4]
The assumptions:
Element in the array identified by the value.
The uniqueness of the elements guaranteed.
Any element can be moved to any place.
Null values not involved on any side.
The value is contained in the array if not we have 2 options. First is do nothing and second handling of this exception by exception mechanism. It's an extreme case that can happen only because of some BUG
Arrays are 1-dimensional.
General assumptions:
Array elements are UNIQUE NOT NULL.
Arrays are 1-dimensional with standard subscripts (1..N). See:
Normalize array subscripts for 1-dimensional array so they start with 1
Simple solution
CREATE FUNCTION f_array_move_element_simple(_arr bigint[], _elem bigint, _pos int)
RETURNS bigint[] LANGUAGE sql IMMUTABLE AS
'SELECT a1[:_pos-1] || _elem || a1[_pos:] FROM array_remove(_arr, _elem) a1'
All fine & dandy, as long as:
The given element is actually contained in the array.
The given position is between 1 and the length of the array.
Proper solution
CREATE FUNCTION f_array_move_element(_arr ANYARRAY, _elem ANYELEMENT, _pos int)
RETURNS ANYARRAY AS
$func$
BEGIN
IF _pos IS NULL OR _pos < 1 THEN
RAISE EXCEPTION 'Target position % not allowed. Must be a positive integer.', _pos;
ELSIF _pos > array_length(_arr, 1) THEN
RAISE EXCEPTION 'Target position % not allowed. Cannot be greater than length of array.', _pos;
END IF;
CASE array_position(_arr, _elem) = _pos -- already in position, return org
WHEN true THEN
RETURN _arr;
WHEN false THEN -- remove element
_arr := array_remove(_arr, _elem);
ELSE -- element not found
RAISE EXCEPTION 'Element % not contained in array %!', _elem, _arr;
END CASE;
RETURN _arr[:_pos-1] || _elem || _arr[_pos:];
END
$func$ LANGUAGE plpgsql IMMUTABLE;
Exceptions are raised if any of the additional assumptions for the simple func are violated.
The "proper" function uses polymorphic types and works for any data type, not just bigint - as long as array and element type match.
db<>fiddle here
Postgresql supports slicing and appending so:
SELECT c, c[2:3] || c[1] || c[4:] AS result
FROM (SELECT ARRAY[1, 2, 3, 4] c) sub
db<>fiddle demo
Another variant using 'WITH .. SELECT ..' avoid searching elements by value, just array element numbers. jsonb[] with big jsons for example.
test_model.data - field to update.
:idx_from, :idx_to - placeholders, 1-based.
WITH from_removed AS (
SELECT
test_model.id,
ARRAY_CAT(
data[: :idx_from - 1],
data[:idx_from + 1 :]
) AS "d"
FROM test_model
)
UPDATE test_model AS source
SET data =
from_removed.d[: :idx_to - 1] ||
data[:idx_from : :idx_from] ||
from_removed.d[:idx_to :]
FROM from_removed
WHERE source.id = from_removed.id AND source.id = :id

How to check if an array is in a multidimensional array

I'd want to check, using a filtering query, if an array is equal to an element of another multidimensional array which can be considered as an array of arrays.
For example:
Given the multidimensional array {{1,2}, {3,4}, {5,6}} I want to check if a one dimension array of one of the array elements.
Expected results:
Input: {1,2} or {3,4} -> Output: TRUE
Input: {2,3} or {1,5} -> Output: FALSE
I've already tried <#, but it returns TRUE for all the examples cases and I can't use ANY without slicing the multidimensional array.
Does anyone have a solution without using pgplsql?
This does seem like a difficult problem to solve without any pgpsql. However, if this function is utilized, it is much simpler:
https://wiki.postgresql.org/wiki/Unnest_multidimensional_array
CREATE OR REPLACE FUNCTION public.reduce_dim(anyarray)
RETURNS SETOF anyarray AS
$function$
DECLARE
s $1%TYPE;
BEGIN
FOREACH s SLICE 1 IN ARRAY $1 LOOP
RETURN NEXT s;
END LOOP;
RETURN;
END;
$function$
LANGUAGE plpgsql IMMUTABLE;
To use:
create table array_test (arr integer[][]);
insert into array_test (select '{{1,2}, {3,4}, {5,6}}');
select (case when '{1,2}' in (select reduce_dim(arr) from array_test) then true
else false end);
case
------
t
(1 row)
select (case when '{1,4}' in (select reduce_dim(arr) from array_test) then true
else false end);
case
------
f
(1 row)
Simple way: search in array like in string:
select '{{1, 2}, {3, 4}, {5, 6}}'::int[][]::text like '%{1,2}%';
Complex way: decompose array to slices (without plpgsql):
with t(x) as (values('{{1, 2}, {3, 4}, {5, 6}}'::int[][]))
select *
from t
where (
select bool_or(x[s:s] = '{{1,3}}') from generate_subscripts(x,1) as s);

appending to multi dimensional arrays in postgres

I am trying to create a multi-column aggregate function that needs to accumulate all the column values for later processing.
CREATE OR REPLACE FUNCTION accumulate_scores(prev integer[][],l1 integer,
l2 integer,l3 integer) RETURNS integer[][] AS
$$
BEGIN
prev[1] = array_append(prev[1],l1);
prev[2] = array_append(prev[2],l2);
prev[3] = array_append(prev[3],l3);
return prev;
END
$$ LANGUAGE plpgsql;
CREATE AGGREGATE my_aggregate(integer,integer,integer)(
SFUNC = accumulate_scores,
STYPE = integer[][],
INITCOND = '{}'
);
select accumulate_scores(ARRAY[1]|| ARRAY[[1],[1]],2,3,4);
I get this error
ERROR: function array_append(integer, integer) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
How do i accumulate these values into a multi dimensional array?
Edit: i have tried with array_cat and get the same error. thought array_append might be right since prev[1] is a uni dimensional array
You function & aggregate function would "work" like this, but it would only create a 1-dimenstional array:
CREATE OR REPLACE FUNCTION accumulate_scores(prev integer[][]
,l1 integer
,l2 integer
,l3 integer)
RETURNS integer[][] AS
$func$
BEGIN
prev := prev || ARRAY[l1,l2,l3];
RETURN prev;
END
$func$ LANGUAGE plpgsql;
CREATE AGGREGATE my_aggregate(integer,integer,integer)(
SFUNC = accumulate_scores,
STYPE = integer[][],
INITCOND = '{}'
);
The declaration RETURNS integer[][] is equivalent to just RETURNS integer[]. For Postgres all arrays on the same base element are the same type. Additional brackets are for documentation only and ignored.
To actually produce a multi-dimensional array, you don't need a custom function. Just a custom aggregate function:
CREATE AGGREGATE array_agg_mult (anyarray) (
SFUNC = array_cat
,STYPE = anyarray
,INITCOND = '{}'
);
Then you can:
SELECT array_agg_mult(ARRAY[ARRAY[l1,l2,l3]]) -- note the two array layers!
from tbl;
More details here:
Selecting data into a Postgres array
This is how I understand the question
select array[
array_agg(l1),
array_agg(l2),
array_agg(l3)
]
from (values
(1, 9, 1, 2, 3), (2, 9, 4, 5, 6)
) s(id, bar, l1, l2, l3)
group by bar;
array
---------------------
{{1,4},{2,5},{3,6}}
In case somebody encount some issue following erwin's great answer.
before postgresql 14 follow Erwin's answer.
for postgresql 14, we need tiny tweak.
create or replace aggregate
array_agg_mult(anycompatiblearray)
(sfunc = array_cat,
stype = anycompatiblearray,
initcond = '{}');

Resources