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 = '{}');
Related
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
I have two arrays [1,2,3,4,7,6] and [2,3,7] in PostgreSQL which may have common elements. What I am trying to do is to exclude from the first array all the elements that are present in the second.
So far I have achieved the following:
SELECT array
(SELECT unnest(array[1, 2, 3, 4, 7, 6])
EXCEPT SELECT unnest(array[2, 3, 7]));
However, the ordering is not correct as the result is {4,6,1} instead of the desired {1,4,6}.
How can I fix this ?
I finally created a custom function with the following definition (taken from here) which resolved my issue:
create or replace function array_diff(array1 anyarray, array2 anyarray)
returns anyarray language sql immutable as $$
select coalesce(array_agg(elem), '{}')
from unnest(array1) elem
where elem <> all(array2)
$$;
I would use ORDINALITY option of UNNEST and put an ORDER BY in the array_agg function while converting it back to array. NOT EXISTS is preferred over except to make it simpler.
SELECT array_agg(e order by id)
FROM unnest( array[1, 2, 3, 4, 7, 6] ) with ordinality as s1(e,id)
WHERE not exists
(
SELECT 1 FROM unnest(array[2, 3, 7]) as s2(e)
where s2.e = s1.e
)
DEMO
More simple, NULL support, probably faster:
select array(
select v
from unnest(array[2,2,null,1,3,3,4,5,null]) with ordinality as t(v, pos)
where array_position(array[3,3,5,5], v) is null
order by pos
);
Result: {2,2,null,1,4,null}
Function array_diff() with tests.
Postgres is unfortunately lacking this functionality. In my case, what I really needed to do was to detect cases where the array difference was not empty. In that specific case you can do that with the #> operator which means "Does the first array contain the second?"
ARRAY[1,4,3] #> ARRAY[3,1,3] → t
See doc
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);
Code is very simple. Declaring two tables, a simple and a multi-dimensional:
Player = {X_Pos = 1, Y_Pos = 1, Current_Sprite_Num = 100}
for j=1, Max_col_length do -- value ofMax_col_length doesn't matter here; positive integer anyway
MapLayer_B[j] = {}
for i=1, Max_row_length do --same here
MapLayer_B[j][i] = 1
end
end
Then I try to do this operation:
MapLayer_B[Player[X_Pos]][Player[Y_Pos]] = Player[Current_Sprite_Num]
It should replace the Player[X_Pos]th element of the Player[Y_Pos]th rowtable in MapLayer_B. Instead, I got this error with LÖVE compiler:
Error: attempt to index field '?' (a nil value)
I don't really get, why it happens, since all elements of both MapLayer_B andPlayer tables are declared and not kept in nil.
Any ideas?
You need to use Player.X_Pos instead of Player[X_Pos] and so on.
The brackets notation will interpret "X_Pos" as a variable and try to access taht key instead (the reason for the error is that undefined variables default to null)
t = {a = 17}
print( t.a ) --dot notation is simpler
print( t["a"] ) --bracket notation expects a string
key = "a" --that string can be from a variable
print( t[key] )
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.