Updating json array field by removing specific values in Postgres - arrays

Let's say I have a table called SomeTable with a primary key row_id and json array field called some_json.
some_json would look something like this:
[{'some_key': 'some_value_1'}, {'some_key': 'some_value_2'},
{'some_key': 'some_value_3'}, {'some_key': 'some_value_4'}]
and I have a function that takes a text array parameter called values_to_remove like this:
['some_value_2', 'some_value_3', etc]
I want to update some_json by removing all of its json objects that contain a text value that's also in the values_to_remove array. I have a rough idea of what I need to do but don't know how to piece it all together with the correct syntax, so forgive me if doesn't make sense.
Here's what I have so far (forgive me for butchering the syntax):
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
BEGIN
UPDATE SomeTable
SET some_json = array_to_json(array_remove(ARRAY(SELECT *
FROM json_array_elements(some_json)),
(SELECT *
FROM json_array_elements(some_json),
unnest(values_to_remove)
WHERE some_json->>some_key = values_to_remove.value
)))
WHERE row_id = removal_id;
END;
$$ LANGUAGE plpgsql;
What's the correct solution for achieving this? Thanks.

Here is the function made with plain SQL
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
UPDATE sometable
SET some_json =
(SELECT json_agg(ae)
FROM (
SELECT json_array_elements(some_json) AS ae from sometable WHERE row_id = removal_id
) sq1
WHERE NOT ARRAY[(SELECT * FROM json_to_record(ae) AS x(some_key text))] <# ARRAY['some_value_3', 'some_value_2'])
WHERE row_id = removal_id;
$$ LANGUAGE SQL;

Related

Postgres adding unexisting varchar array

I have the following table:
CREATE TABLE fun (
id uuid not null,
tag varchar[] NOT NULL,
CONSTRAINT fun_pkey PRIMARY KEY(id, tag)
);
CREATE UNIQUE INDEX idx_fun_id ON fun USING btree (id);
Then I inserted a data into the table
insert into fun (id, tag)
values('d7f17de9-c1e9-47ba-9e3d-cd1021c644d2', array['123','234'])
So currently, the value of my tag is ["123", "234"]
How can I add the value of the array, and ignore any of the existing varchar, only adding the non-existing one?
currently, this is how I approach it
update fun
set tag = tag || array['234','345']
where id = 'd7f17de9-c1e9-47ba-9e3d-cd1021c644d2'
but my tag will become ["123", "234", "234", "345"]. The value of 234 becomes a duplicated one. What I need to achieve is the value of the tag becomes ["123", "234", "345"]
There is no built-in function to only append unique elements, but it's easy to write one:
create function append_unique(p_one text[], p_two text[])
returns text[]
as
$$
select array(select *
from unnest(p_one)
union
select *
from unnest(p_two));
$$
language sql
immutable;
Then you can use it like this:
update fun
set tag = append_unique(tag,array['234','345'])
where id = 'd7f17de9-c1e9-47ba-9e3d-cd1021c644d2'
Note that this does not preserve the order of the items.
A function that preserves the order of the elements of the existing array and appends the elements of the second one in the order provided would be:
create function append_unique(p_one text[], p_two text[])
returns text[]
as
$$
select p_one||array(select x.item
from unnest(p_two) with ordinality as x(item,idx)
where x.item <> all (p_one)
order by x.idx);
$$
language sql
immutable;

how to load a query into arrays

I need to load some queries into vectors to avoid temporary tables in a function:
create table mytable
( eref serial primarykey,
edia date,
eimpte numeric);
---
CREATE OR REPLACE FUNCTION peps(rseller integer)
RETURNS void AS
$BODY$
declare
dep_dia date[] := '{}';
dep_impte numeric[]:= '{}';
dep_ref integer[]:= '{}';
ndepositos integer :=0;
rec record;
begin
for rec in
select eref, edia, eimpte from mytable order by edia, eimpte
loop
ndepositos:=ndepositos+1;
dep_dia[ndepositos] :=edia;
dep_impte[ndepositos]:=eimpte;
dep_ref[ndepositos] :=eref;
end loop;
raise notice ' ndeps %', ndepositos;
end
$BODY$
language plpgsql volatile;
it does not work:
ERROR: column "edia" does not exist
LINE 1: SELECT edia
^
what am I doing wrong?
Thanks in advance
Don't loop! Postgres provides a great function for this:
SELECT array_agg(eref), array_agg(edia), array_agg(eimpte)
FROM (SELECT * from mytable order by edia, eimpte) AS foo
INTO your variables
By putting the order by in a subquery, the aggregate functions will get the values in the order you want. This should be faster than looping.
You are not having edia column while creating the table. Looking at reamining code, I feel your create table query should be like following:
create table mytable
( edia serial primarykey,
eref date,
eimpte numeric);

Postgresql stored procedure return select result set

In Microsoft SQL server I could do something like this :
create procedure my_procedure #argument1 int, #argument2 int
as
select *
from my_table
where ID > #argument1 and ID < #argument2
And that would return me table with all columns from my_table.
Closest thing to that what I managed to do in postgresql is :
create or replace function
get_test()
returns setof record
as
$$ select * from my_table $$
language sql
or i could define my table type, but manually recreating what technically already exists is very impractical.
create or replace function
get_agent_summary()
returns table (
column1 type, column2 type, ...
)
as
$$
begin
return query select col1, col2, ... from my_existing_table;
...
and it is pain to maintain.
So, how can I easily return resultset without redefining defining every single column from table that I want to return?
In Postgres a table automatically defines the corresponding type:
create or replace function select_my_table(argument1 int, argument2 int)
returns setof my_table language sql as $$
select *
from my_table
where id > argument1 and id < argument2;
$$;
select * from select_my_table(0, 2);
The syntax is more verbose than in MS SQL Server because you can create functions in one of several languages and functions may be overloaded.

Stored procedure syntax with IN condition

(1)
=>CREATE TABLE T1(id BIGSERIAL PRIMARY KEY, name TEXT);
CREATE TABLE
(2)
=>INSERT INTO T1
(name) VALUES
('Robert'),
('Simone');
INSERT 0 2
(3)
SELECT * FROM T1;
id | name
----+--------
1 | Robert
2 | Simone
(2 rows)
(4)
CREATE OR REPLACE FUNCTION test_me(id_list BIGINT[])
RETURNS BOOLEAN AS
$$
BEGIN
PERFORM * FROM T1 WHERE id IN ($1);
IF FOUND THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$$
LANGUAGE 'plpgsql';
CREATE FUNCTION
My problem is when calling the procedure. I'm not able to find an example on the net showing how to pass a list of values of type BIGINT (or integer, whatsoever).
I tried what follows without success (syntax errors):
First syntax:
eway=> SELECT * FROM test_me('{1,2}'::BIGINT[]);
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Second syntax:
eway=> SELECT * FROM test_me('{1,2}');
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Third syntax:
eway=> SELECT * FROM test_me(ARRAY [1,2]);
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Any clues about a working syntax?
It's like the parser was trying to translate a BIGINT to BIGINT[] in the PEFORM REQUEST but it doesn't make any sense to me...
All your syntax variants to pass an array are correct.
Pass array literal to PostgreSQL function
The problem is with the expression inside the function. You can test with the ANY construct like #Mureinik provided or a number of other syntax variants. In any case run the test with an EXISTS expression:
CREATE OR REPLACE FUNCTION test_me(id_list bigint[])
RETURNS bool AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM t1 WHERE id = ANY ($1)) THEN
RETURN true;
ELSE
RETURN false;
END IF;
END
$func$ LANGUAGE plpgsql STABLE;
Notes
EXISTS is shortest and most efficient:
PL/pgSQL checking if a row exists - SELECT INTO boolean
The ANY construct applied to arrays is only efficient with small arrays. For longer arrays, other syntax variants are faster. Like:
IF EXISTS (SELECT 1 FROM unnest($1) id JOIN t1 USING (id)) THEN ...
How to do WHERE x IN (val1, val2,…) in plpgsql
Don't quote the language name, it's an identifier, not a string: LANGUAGE plpgsql
Simple variant
While you are returning a boolean value, it can be even simpler. It's probably just for the demo, but as a proof of concept:
CREATE OR REPLACE FUNCTION test_me(id_list bigint[])
RETURNS bool AS
$func$
SELECT EXISTS (SELECT 1 FROM t1 WHERE id = ANY ($1))
$func$ LANGUAGE sql STABLE;
Same result.
The easiest way to check if an item is in an array is with = ANY:
CREATE OR REPLACE FUNCTION test_me(id_list BIGINT[])
RETURNS BOOLEAN AS
$$
BEGIN
PERFORM * FROM T1 WHERE id = ANY ($1);
IF FOUND THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$$
LANGUAGE 'plpgsql';

Postgres function with text array and select where in query

I need to create a function like this (scaled down to a minimum) where I send an array of strings that should be matched. But I cant make the query to work.
create or replace function bar(x text[]) returns table (c bigint) language plpgsql as $$
begin
return query select count(1) as counter from my_table where my_field in (x);
end;$$;
and call it like this
select * from bar(ARRAY ['a','b']);
I could try to let the parameter x be a single text string and then use something like
return query execute 'select ... where myfield in ('||x||')';
So how would I make it work with the parameter as an array?
would that be better or worse compared to let the parameter be a string?
Yes, an array is the cleaner form. String matching would leave corner cases where separators and patterns combined match ...
To find strings that match any of the given patterns, use the ANY construct:
CREATE OR REPLACE FUNCTION bar(x text[])
RETURNS bigint LANGUAGE sql AS
$func$
SELECT count(*) -- alias wouldn't visible outside function
FROM my_table
WHERE my_field = ANY(x);
$func$;
count(*) is slightly faster than count(1). Same result.
Note, I am using a plain SQL function (instead of plpgsql). Either has its pros and cons.
That's fixed with the help of unnest that converts an array to a set (btw, the function doesn't have to be plpgsql):
CREATE OR REPLACE FUNCTION bar(x text[]) RETURNS BIGINT LANGUAGE sql AS $$
SELECT count(1) AS counter FROM my_table
WHERE my_field IN (SELECT * FROM unnest(x));
$$;
The problem with using the array seems to be fixed by using
return query select count(1) as counter from my_table where my_field in (array_to_string(x,','));
The point of effiency still remains unsolved.

Resources