Can't append json array into a json array in postgresql function - arrays

I am trying to append a json array or object into a json array but array_append always inserts the json as string.
My result is;
{"{"category_id":8,"category_name":"08 Candy","is_active":true,"category_name_app":"Candy","display_order":7}"}
but i want it to be like this. So i can json_decode them in my code.
[{"category_id":8,"category_name":"08 Candy","is_active":true,"category_name_app":"Candy","display_order":7}]
here is my function logic
for all_categories in select * from categories where is_active = '1' loop
show_at_homepage = 0;
for current_subcat in select * from public."V_category_to_sub_category_w_names" where category_id = all_categories.category_id and sub_category_is_active = '1' loop
select * into product_count from public."V_APP_products_w_sub_categories" where sub_category_id = current_subcat.sub_category_id and store_id = get_store_id and is_deleted='0';
if count(product_count) > 0 then
show_at_homepage = 1;
end if;
end loop;
if show_at_homepage = 1 then
select row_to_json(all_categories) into cat_json;
select array_append(my_json_result_array,cat_json) into my_json_result_array;
end if;
end loop;
return my_json_result_array;

You can apply array_append to any kind of array of postgres data types, including json[], but you can't apply it to a json array which is of type json. In your case, you need to apply the json functions and operators, see the manual, and more specifically the json_build_array function :
Replace
select array_append(my_json_result_array,cat_json) into my_json_result_array
By
select json_build_array(my_json_result_array,cat_json) into my_json_result_array
Or when you use jsonb instead of json which is the manual recommendation :
select jsonb_build_array(my_json_result_array,cat_json) into my_json_result_array

Related

Postgresql how to select a value from multiple jsons inside a array on a jsonB column

I have this table
create table <table_name>(attr jsonb)
And this is the data inside
{
"rules": [
{
"id": "foo",
"name": "test_01",
...
},
{
"id": "bar",
"name": "test_02",
...
}
]
}
What I want is to select both names, what I have accomplished so far is this
select attr -> 'rules' -> 0 -> 'name' from <table_name>;
which returns test_01
select attr -> 'rules' -> 1 -> 'name' from <table_name>;
which returns test_02
I want to return something like this:
test_01,test_02
or if it's possible to return them in multiple lines, that would be even better
This is a sample data to show my problem, for reasons beyond my control, it's not possible to store each rule on a distinct line
You can use jsonb_array_length together with generate_series to get each name. Then use string_agg to aggregate list of names. Without plpgsql and with a single statement. (see demo)
with jl(counter) as ( select jsonb_array_length(attr->'rules') from table_name )
select string_agg(name,' ') "Rule Names"
from (select attr->'rules'-> n ->> 'name' name
from table_name
cross join ( select generate_series(0,counter-1) from jl ) gs(n)
) rn;
if anyone else get stuck on a situation like this, this is the solution the I found
create or replace function func_get_name() RETURNS text
language 'plpgsql'
AS $$
declare
len character varying(255);
names character varying(255);
res character varying(255);
begin
select jsonb_array_length(attr->'rules') into len from <table_name>;
res := '';
for counter in 0..len loop
select attr->'rules'-> counter ->> 'name'
into names
from <table_name>;
if names is not null then
res := res || ' ' || names;
end if;
end loop;
return res;
end;
$$
select func_get_name();
it's a solution: yes, it's a good solution: I have no ideia

BigQuery: extract keys from json object, convert json from object to key-value array

I have a table with a column which contains a json-object, the value type is always a string.
I need 2 kind of information:
a list of the json keys
convert the json in an array of key-value pairs
This is what I got so far, which is working:
CREATE TEMP FUNCTION jsonObjectKeys(input STRING)
RETURNS Array<String>
LANGUAGE js AS """
return Object.keys(JSON.parse(input));
""";
CREATE TEMP FUNCTION jsonToKeyValueArray(input STRING)
RETURNS Array<Struct<key String, value String>>
LANGUAGE js AS """
let json = JSON.parse(input);
return Object.keys(json).map(e => {
return { "key" : e, "value" : json[e] }
});
""";
WITH input AS (
SELECT "{\"key1\": \"value1\", \"key2\": \"value2\"}" AS json_column
UNION ALL
SELECT "{\"key1\": \"value1\", \"key3\": \"value3\"}" AS json_column
UNION ALL
SELECT "{\"key5\": \"value5\"}" AS json_column
)
SELECT
json_column,
jsonObjectKeys(json_column) AS keys,
jsonToKeyValueArray(json_column) AS key_value
FROM input
The problem is that FUNCTION is not the best in term of compute optimization, so I'm trying to understand if there is a way to use plain SQL to achieve these 2 needs (or 1 of them at least) using only SQL w/o functions.
Below is for BigQuery Standard SQL
#standardsql
select
json_column,
array(select trim(split(kv, ':')[offset(0)]) from t.kv kv) as keys,
array(
select as struct
trim(split(kv, ':')[offset(0)]) as key,
trim(split(kv, ':')[offset(1)]) as value
from t.kv kv
) as key_value
from input,
unnest([struct(split(translate(json_column, '{}"', '')) as kv)]) t
If to apply to sample data from your question - output is

Postgresql: Append object to a list

I have data that has a key as string and value is a list of json, looks like
ID|data
A1|{key1:[{k1:v1,k2:v2},{{k3:v3,k4:v4}]}
I want to append json, say, {k9:v9,k7:v6} to this list for the key1, something like-
ID|data
A1|{key1:[{k1:v1,k2:v2},{{k3:v3,k4:v4},{k9:v9,k7:v6}]}
I have tried jsonb_set and other functions but they were of no use, example-
UPDATE tbl_name
SET data = jsonb_set(data,'{key1,1}','{k9:v9,k7:v6}'::jsonb) where ID = 'A1'
You need to use jsonb_insert() function in order to append that part, after fixing the format of JSONB value ( otherwise you'd get "ERROR: invalid input syntax for type json" ) :
UPDATE tbl_name
SET data = jsonb_insert(data,'{key1,1}','{"k9":"v9","k7":"v6"}'::jsonb)
WHERE ID = 'A1'
Demo

How to update JSON array with PostgreSQL

I have the following inconvenience, I want to update a key of an JSON array using only PostgreSQL. I have the following json:
[
{
"ch":"1",
"id":"12",
"area":"0",
"level":"Superficial",
"width":"",
"length":"",
"othern":"5",
"percent":"100",
"location":" 2nd finger base"
},
{
"ch":"1",
"id":"13",
"area":"0",
"level":"Skin",
"width":"",
"length":"",
"othern":"1",
"percent":"100",
"location":" Abdomen "
}
]
I need to update the "othern" to another number if the "othern" = X
(X is any number that I pass to the query. Example, update othern if othern = 5).
This JSON can be much bigger, so I need something that can iterate in the JSON array and find all the "othern" that match X number and replace with the new one. Thank you!
I have tried with these functions json of Postgresql, but I do not give with the correct result:
SELECT * FROM jsonb_to_recordset('[{"ch":"1", "id":"12", "area":"0", "level":"Superficial", "width":"", "length":"", "othern":"5", "percent":"100", "location":" 2nd finger base"}, {"ch":"1", "id":"13", "area":"0", "level":"Skin", "width":"", "length":"", "othern":"1", "percent":"100", "location":" Abdomen "}]'::jsonb)
AS t (othern text);
I found this function in SQL that is similar to what I need but honestly SQL is not my strength:
CREATE OR REPLACE FUNCTION "json_array_update_index"(
"json" json,
"index_to_update" INTEGER,
"value_to_update" anyelement
)
RETURNS json
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
SELECT concat('[', string_agg("element"::text, ','), ']')::json
FROM (SELECT CASE row_number() OVER () - 1
WHEN "index_to_update" THEN to_json("value_to_update")
ELSE "element"
END "element"
FROM json_array_elements("json") AS "element") AS "elements"
$function$;
UPDATE plan_base
SET atts = json_array_update_index([{"ch":"1", "id":"12", "area":"0", "level":"Superficial", "width":"", "length":"", "othern":"5", "percent":"100", "location":" 2nd finger base"}, {"ch":"1", "id":"13", "area":"0", "level":"Skin", "width":"", "length":"", "othern":"1", "percent":"100", "location":" Abdomen "}], '{"othern"}', '{"othern":"71"}'::json)
WHERE id = 2;
The function you provided changes a JSON input, gives out the changed JSON and updates a table parallel.
For a simple update, you don't need a function:
demo:db<>fiddle
UPDATE mytable
SET myjson = s.json_array
FROM (
SELECT
jsonb_agg(
CASE WHEN elems ->> 'othern' = '5' THEN
jsonb_set(elems, '{othern}', '"7"')
ELSE elems END
) as json_array
FROM
mytable,
jsonb_array_elements(myjson) elems
) s
jsonb_array_elements() expands the array into one row per element
jsonb_set() changes the value of each othern field. The relevant JSON objects can be found with a CASE clause
jsonb_agg() reaggregates the elements into an array again.
This array can be used to update your column.
If you really need a function which gets the parameters and returns the changed JSON, then this could be a solution. Of course, this doesn't execute an update. I am not quite sure if you want to achieve this:
demo:db<>fiddle
CREATE OR REPLACE FUNCTION json_array_update_index(_myjson jsonb, _val_to_change int, _dest_val int)
RETURNS jsonb
AS $$
DECLARE
_json_output jsonb;
BEGIN
SELECT
jsonb_agg(
CASE WHEN elems ->> 'othern' = _val_to_change::text THEN
jsonb_set(elems, '{othern}', _dest_val::text::jsonb)
ELSE elems END
) as json_array
FROM
jsonb_array_elements(_myjson) elems
INTO _json_output;
RETURN _json_output;
END;
$$ LANGUAGE 'plpgsql';
If you want to combine both as you did in your question, of course, you can do this:
demo:db<>fiddle
CREATE OR REPLACE FUNCTION json_array_update_index(_myjson jsonb, _val_to_change int, _dest_val int)
RETURNS jsonb
AS $$
DECLARE
_json_output jsonb;
BEGIN
UPDATE mytable
SET myjson = s.json_array
FROM (
SELECT
jsonb_agg(
CASE WHEN elems ->> 'othern' = '5' THEN
jsonb_set(elems, '{othern}', '"7"')
ELSE elems END
) as json_array
FROM
mytable,
jsonb_array_elements(myjson) elems
) s
RETURNING myjson INTO _json_output;
RETURN _json_output;
END;
$$ LANGUAGE 'plpgsql';

How do I call a declared array variable in a where clause in postgres?

I am trying to build a declared array from all the dogs that share the same family_id and query the dog_characteristics table using the array.
CREATE OR REPLACE FUNCTION update_dog_characteristics_guarantor_id()
RETURNS trigger AS $$
DECLARE dog_ids INT[];
BEGIN
SELECT id into dog_ids FROM dogs WHERE dogs.family_id = OLD.id;
IF ((OLD.family_id IS NOT NULL) && ((SELECT COUNT(*) FROM dog_ids) > 0)) THEN
UPDATE
dog_characteristics
SET
guarantor_id = NEW.guarantor_id
WHERE
dog_characteristics.account_id = OLD.account_id
AND dog_characteristics.dog_id IN ANY(dog_ids);
RETURN NULL;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
What I have tried
AND dog_characteristics.dog_id = ANY(dog_ids);
AND dog_characteristics.dog_id = ANY(dog_ids::int[]);
AND dog_characteristics.dog_id IN (dog_ids::int[]);
AND dog_characteristics.dog_id IN (dog_ids);
AND dog_characteristics.dog_id IN (ARRAY(dog_ids));
AND dog_characteristics.dog_id IN ARRAY(dog_ids);
AND dog_characteristics.dog_id IN implode( ',', dog_ids);
Most common error
ERROR: malformed array literal: "672"
DETAIL: Array value must start with "{" or dimension information.
CONTEXT: PL/pgSQL function update_dog_characteristics_guarantor_id() line 5 at SQL statement
There are multiple errors in your trigger function.
As dog_ids is declared as an array, the result of the first select has to be an array as well. To do that, you need to aggregate all IDs that are returned from the query.
So the first select statement should be
select array_agg(id) --<< aggregate all IDs into an array
into dog_ids
FROM dogs
WHERE dogs.family_id = OLD.id;
To check if an array has elements, you can't use select count(*), you need to use use array_length() or cardinality().
The && is not the "AND" operator in SQL - that's AND - so the if should be:
IF OLD.family_id IS NOT NULL AND cardinality(dog_ids) > 0 THEN
...
END IF;
The where condition on the array should be:
AND dog_characteristics.dog_id = ANY(dog_ids);

Resources