How to update a jsonb column in postgresql which is just an array of values and no keys - postgresql

I need to update a jsonb column which is called "verticals" and the array of values it holds are like HOM, BFB etc. There are no keys in the array.
Table: Product(verticals jsonb, code int)
sample value stored in "verticals" column is
[HOM,rst,NLF,WELSAK,HTL,TRV,EVCU,GRT]
I need to update the value 'HOM' to 'XXX' in the column "verticals" where code =1
My expected output is
[XXX,rst,NLF,WELSAK,HTL,TRV,EVCU,GRT]

Because you chose to store your data in a de-normalized way, updating it is more complicated then it has to be.
You need to first unnest the array (essentially normalizing the data), replace the values, then aggregate them back and update the column:
update product p
set verticals = t.verticals
from (
select jsonb_agg(case when x.v = 'HOM' then 'XXX' else x.v end order by idx) as verticals
from product p2, jsonb_array_elements_text(p2.verticals) with ordinality as x(v,idx)
where code = 1
) t
where p.code = t.code;
This assumes that product.code is a primary (or unique) key!
Online example: http://rextester.com/KZQ65481
If the order of the array elements is not important, this gets easier:
update product
set verticals = (verticals - 'HOM')||'["XXX"]'
where code = 1;
This removes the element 'HOM' from the array (regardless of the posisition) and then appends 'XXX' to the end of the array.

You should use jsonb_set(target jsonb, path text[], new_value jsonb[, create_missing boolean]) and array_position() OR array_replace(anyarray, anyelement, anyelement)
https://www.postgresql.org/docs/9.5/static/functions-json.html
https://www.postgresql.org/docs/10/static/functions-array.html

Related

How to modify or remove a specific JSON object from JSON array stored in jsonb column type in PostgreSQL using where clause?

In my Postgres database, I have one of the table columns having jsonb datatype. In that column, I am storing the JSON array. Now, I want to remove or modify a specific JSON object inside the array.
My JSON array looks like
[
{
"ModuleId": 1,
"ModuleName": "XYZ"
},
{
"ModuleId": 2,
"ModuleName": "ABC"
}
]
Now, I want to perform two operations:
How can I remove the JSON object from the above array having ModuleId as 1?
How can I modify the JSON object i.e. change the ModuleName as 'CBA' whose ModuleId is 1?
Is there a way through which I could perform queried directly on JSON array?
Note: Postgres version is 12.0
Both problems require unnesting and aggregating back the (modified) JSON elements. For both problems I would create a function to make that easier to use.
create function remove_element(p_value jsonb, p_to_remove jsonb)
returns jsonb
as
$$
select jsonb_agg(t.element order by t.idx)
from jsonb_array_elements(p_value) with ordinality as t(element, idx)
where not t.element #> p_to_remove;
$$
language sql
immutable;
The function can be used like this, e.g. in an UPDATE statement:
update the_table
set the_column = remove_element(the_column, '{"ModuleId": 1}')
where ...
For the second problem a similar function comes in handy.
create function change_value(p_value jsonb, p_what jsonb, p_new jsonb)
returns jsonb
as
$$
select jsonb_agg(
case
when t.element #> p_what then t.element||p_new
else t.element
end order by t.idx)
from jsonb_array_elements(p_value) with ordinality as t(element, idx);
$$
language sql
immutable;
The || operator will overwrite an existing key, so this effectively replaces the old name with the new name.
You can use it like this:
update the_table
set the_column = change_value(the_column, '{"ModuleId": 1}', '{"ModuleName": "CBA"}')
where ...;
I think passing the JSON values is a bit more flexible then hardcoding the keys which makes the use of the function very limited. The first function could also be used to remove array elements by comparing multiple keys.
If you don't want to create the functions, replace the function call with the select from the functions.
For your both cases, consider using a subquery including dynamic logic to determine the index of the element which contains the value for ModuleId key equal to 1.
For the First case, use #- operator :
WITH s AS
(
SELECT ('{'||idx-1||'}')::text[] AS path, jsdata
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'ModuleId'='1'
)
UPDATE tab
SET jsdata = s.jsdata #- path
FROM s
and for the second case, use jsonb_set() function with path coming from thesubquery :
WITH s AS
(
SELECT ('{'||idx-1||',ModuleId}')::text[] AS path
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'ModuleId'='1'
)
UPDATE tab
SET jsdata = jsonb_set(jsdata,s.path,'"CBA"',false)
FROM s
Demo

Fetch rows from postgres table which contains a specific id in jsonb[] column

I have a details table with adeet column defined as jsonb[]
a sample value stored in adeet column is as below image
Sample data stored in DB :
I want to return the rows which satisfies id=26088 i.e row 1 and 3
I have tried array operations and json operations but it does'nt work as required. Any pointers
Obviously the type of the column adeet is not of type JSON/JSONB, but maybe VARCHAR and we should fix the format so as to convert into a JSONB type. I used replace() and r/ltrim() funcitons for this conversion, and preferred to derive an array in order to use jsonb_array_elements() function :
WITH t(jobid,adeet) AS
(
SELECT jobid, replace(replace(replace(adeet,'\',''),'"{','{'),'}"','}')
FROM tab
), t2 AS
(
SELECT jobid, ('['||rtrim(ltrim(adeet,'{'), '}')||']')::jsonb as adeet
FROM t
)
SELECT t.*
FROM t2 t
CROSS JOIN jsonb_array_elements(adeet) j
WHERE (j.value ->> 'id')::int = 26088
Demo
You want to combine JSONB's <# operator with the generic-array ANY construct.
select * from foobar where '{"id":26088}' <# ANY (adeet);

How to lower-case all the elements of a JSONB array of strings of each row in a table

I have a table with a field called "data" which is of JSONB type. The content of "data" is an object with one of the fields called "associated_emails", which is an array of strings.
I need to update the existing table so that the content of "associated_emails" is all lower-case. How to achieve that? This is my attempt so far (it triggers error: ERROR: cannot extract elements from a scalar)
update mytable my
set
"data" = safe_jsonb_set(
my."data",
'{associated_emails}',
to_jsonb(
lower(
(
SELECT array_agg(x) FROM jsonb_array_elements_text(
coalesce(
my."data"->'associated_emails',
'{}'::jsonb
)
) t(x)
)::text[]::text
)::text[]
)
)
where
my.mytype = 'something';
You would like to use JSONB_SET and UPDATE the column with something like given below below:
UPDATE jsonb_test
SET data = JSONB_SET(data, '{0,associated_emails}',
JSONB(LOWER(data ->> 'associated_emails'::TEXT)));

Build jsonb array from jsonb field

I have column options with type jsonb , in format {"names": ["name1", "name2"]} which was created with
UPDATE table1 t1 SET options = (SELECT jsonb_build_object('names', names) FROM table2 t2 WHERE t2.id= t1.id)
and where names have type jsonb array.
SELECT jsonb_typeof(names) FROM table2 give array
Now I want to extract value of names as jsonb array. But query
SELECT jsonb_build_array(options->>'names') FROM table
gave me ["[\"name1\", \"name2\"]"], while I expect ["name1", "name2"]
How can I get value in right format?
The ->> operator will return the value of the field (in your case, a JSON array) as a properly escaped text. What you are looking for is the -> operator instead.
However, note that using the jsonb_build_array on that will return an array containing your original array, which is probably not what you want either; simply using options->'names' should get you what you want.
Actually, you don't need to use jsonb_build_array() function.
Use select options -> 'names' from table; This will fix your issue.
jsonb_build_array() is for generating the array from jsonb object. You are following wrong way. That's why you are getting string like this ["[\"name1\", \"name2\"]"].
Try to execute this sample SQL script:
select j->'names'
from (
select '{"names": ["name1", "name2"]}'::JSONB as j
) as a;

Zend_Db_Adapter_Mysqli::fetchAssoc() I don't want primary keys as array indexes!

According to ZF documentation when using fetchAssoc() the first column in the result set must contain unique values, or else rows with duplicate values in the first column will overwrite previous data.
I don't want this, I want my array to be indexed 0,1,2,3... I don't need rows to be unique because I won't modify them and won't save them back to the DB.
According to ZF documentation fetchAll() (when using the default fetch mode, which is in fact FETCH_ASSOC) is equivalent to fetchAssoc(). BUT IT'S NOT.
I've used print_r()function to reveal the truth.
print_r($db->fetchAll('select col1, col2 from table'));
prints
Array
(
[0] => Array
(
[col1] => 1
[col2] => 2
)
)
So:
fetchAll() is what I wanted.
There's a bug in ZF documentation
From http://framework.zend.com/manual/1.11/en/zend.db.adapter.html
The fetchAssoc() method returns data in an array of associative arrays, regardless of what value you have set for the fetch mode, **using the first column as the array index**.
So if you put
$result = $db->fetchAssoc(
'SELECT some_column, other_column FROM table'
);
you'll have as result an array like this
$result['some_column']['other_column']