Postgresql - Renaming the key of all objects in an array of a JSON column - json

PostgreSQL 13
The goal is to rename all src keys in the photos array to image.
I have a table plans which has a column json with a simplified structure similar to the below sample.
{
"id": "some id",
"name": "some name",
"tags": [
{
"keyId": 123,
"valueId": 123
},
{
"keyId": 123,
"valueId": 123
}
],
"score": 123,
"photos": [
{
"src": "someString"
},
{
"src": "someString"
}
],
"payment": true
}
The number of objects in the photos array varies, but in general, it is less than 10, so a non-iterating method would be fine, too.
I tried something like this, but it is only good for modifying the value of a key, not the name of the key itself.
UPDATE
plans
SET
json = jsonb_set(json::jsonb, '{photos, 0, src}', '"image"')
;
With the following attempt, I was actually able to rename the key but it overwrites everything else, so only an object with {"image": "someUrl"} is left:
UPDATE
plans
SET
json = (json -> 'photos' ->> 0)::jsonb - 'src' || jsonb_build_object ('image',
json::jsonb -> 'photos' -> 0 -> 'src')
WHERE json::jsonb ? 'photos' = true;
Is there a way to rename keys as expected?

So in the end I used a variation of my initial jsonb_set method. The solution isn't elegant or efficient, but since it is a one-time operation, it was only important to work:
UPDATE
plans
SET
json = jsonb_set(json::jsonb, '{photos, 0, imageUrl}', (json->'photos'->0->'src')::jsonb)
WHERE
json->'photos'->0->'src' IS NOT NULL
;
This query would add the imageUrl key with the existing value of the src key for the first object (position 0) in the photos array. So it left me with src and imageUrl key.
To remove the src key, I ran the following query
UPDATE
plans
SET
json = json::jsonb #- '{ photos, 0, src}'
;
Repeating this as many times as the maximum number of elements in a photos array eventually solved the issue for me.

Related

Postgres - Deleting attributes inside json object of json column

I have a Postgres JSON column (column name = data), in which I wanted to delete all the attributes inside the JSON object.
JSON
{
"headerText": "header_text",
"vendor": {
"id": "some text",
"metadata": 123123,
"startDate": "1234234",
"assetIds": [
"some text"
],
"endDate": "234435",
"publishStart": 12443245,
"publishEnd": 978128123
},
"footerText": "some_text"
}
So, here the attributes inside the vendor json object are dynamic, which means there may be additional attributes.
So I tried the below queries, but was unable to yield the expected result
1. update table_name set data = data::jsonb #- '{vendor.*}'
2. update table_name set data = data::jsonb - '{vendor.*}'::text[]
Expected:
{
"headerText": "header text",
"vendor": {},
"footerText": "some text"
}
Just replace vendor with an empty value by appending it.
update table_name
set data = data || '{"vendor": {}}'
This requires data to be defined as jsonb (which it should be). If it's not, you need to cast it: data::jsonb || ....
If you don't need the vendor key at all, you can also do:
update table_name
set data = data - 'vendor'
which completely removes the key from the value (so it results in {"footerText": "some_text", "headerText": "header_text"})
You can use concatenation to overwrite values in (postgres) json:
update table_name set data = data::jsonb || json_build_object('vendor', json_build_object())::jsonb

HOW TO update existing jsonb array attribute for a single item in the array

'{"user_profile":[
{
"profile_name":"customer",
"user_status":"NEW","user_state":"pending",
"s3url":["file1","abc/1/clients/51/clients/acmepm1/"],
"groups":["g1","g2"]
},
{
"profile_name":"admin staff",
"user_status":"NEW","user_state":"admin upload",
"s3url":"abc/"
}
]}'
I would like to change "groups":["g1","g2"] to "groups":["group1","group2"] for "profile_name":"customer"
I could get this far
update users set user_details=(select elem -'groups'||'{"groups":["a","b"]}' from users u
cross join jsonb_array_elements(user_details->'user_profile') elem --with ordinality arr(user_details,index)
where elem->>'profile_name'='customer' )
But this is not correct. As this removes the "admin staff" profile completely, and also the Array "Customer_profile" is gone.
the jsonb becomes
{"s3url": ["file1", "abc/1/clients/51/clients/acmepm1/"], "groups": ["a", "b"], "user_state": "pending", "user_status": "NEW", "profile_name": "customer"}
How do I achieve the update I am looking for?
To replace the array in the jsonb value you show, use
SELECT jsonb_set(
'{"user_profile":
[
{"profile_name":"customer",
"user_status":"NEW",
"user_state":"pending",
"s3url":
["file1","abc/1/clients/51/clients/acmepm1/"],
"groups":
["g1","g2"]
},
{"profile_name":"admin staff",
"user_status":"NEW",
"user_state":"admin upload",
"s3url":"abc/"
}
]
}'::jsonb,
'{user_profile,0,groups}',
["group1","group2"]
);
See the documentation for details about jsonb_set. The first array element is referenced as "0".

Update JSON Array in Postgres with specific key

I have a complex array which look like following in a table column:
{
"sometag": {},
"where": [
{
"id": "Krishna",
"nick": "KK",
"values": [
"0"
],
"function": "ADD",
"numValue": [
"0"
]
},
{
"id": "Krishna1",
"nick": "KK1",
"values": [
"0"
],
"function": "SUB",
"numValue": [
"0"
]
}
],
"anotherTag": [],
"TagTag": {
"tt": "tttttt",
"tt1": "tttttt"
}
In this array, I want to update the function and numValue of id: "Krishna".
Kindly help.
This is really nasty because
Updating an element inside a JSON array always requires to expand the array
On-top: The array is nested
The identfier for the elements to update is a sibling not a parent, which means, you have to filter by a sibling
So I came up with a solution, but I want to disclaim: You should avoid doing this as regular database action! Better would be:
Parsing your JSON in the backend and do the operations in your backend code
Normalize the JSON in your database if that would be a common task, meaning: Create tables with appropriate columns and extract your JSON into the table structure. Do not store entire JSON objects in the database! That would make every single task much more easier and incredible more performant!
demo:db<>fiddle
SELECT
jsonb_set( -- 5
(SELECT mydata::jsonb FROM mytable),
'{where}',
updated_array
)::json
FROM (
SELECT
jsonb_agg( -- 4
CASE WHEN array_elem ->> 'id' = 'Krishna' THEN
jsonb_set( -- 3
jsonb_set(array_elem.value::jsonb, '{function}', '"ADDITION"'::jsonb), -- 2
'{numValue}',
'["0","1"]'::jsonb
)
ELSE array_elem::jsonb END
) as updated_array
FROM mytable,
json_array_elements(mydata -> 'where') array_elem -- 1
) s
Extract the nested array elements into one element per row
Replace function value. Note the casts from type json to type jsonb. That is necessary because there's no json_set() function but only jsonb_set(). Naturally, if you just have type jsonb, the casts are not necessary.
Replace numValue value
Reaggregate the array
Replace the where value of the original JSON object with the newly created array object.

How can Postgres extract parts of json, including arrays, into another JSON field?

I'm trying to convince PostgreSQL 13 to pull out parts of a JSON field into another field, including a subset of properties within an array based on a discriminator (type) property. For example, given a data field containing:
{
"id": 1,
"type": "a",
"items": [
{ "size": "small", "color": "green" },
{ "size": "large", "color": "white" }
]
}
I'm trying to generate new_data like this:
{
"items": [
{ "size": "small" },
{ "size": "large"}
]
}
items can contain any number of entries. I've tried variations of SQL something like:
UPDATE my_table
SET new_data = (
CASE data->>'type'
WHEN 'a' THEN
json_build_object(
'items', json_agg(json_array_elements(data->'items') - 'color')
)
ELSE
null
END
);
but I can't seem to get it working. In this case, I get:
ERROR: set-returning functions are not allowed in UPDATE
LINE 6: 'items', json_agg(json_array_elements(data->'items')...
I can get a set of items using json_array_elements(data->'items') and thought I could roll this up into a JSON array using json_agg and remove unwanted keys using the - operator. But now I'm not sure if what I'm trying to do is possible. I'm guessing it's a case of PEBCAK. I've got about a dozen different types each with slightly different rules for how new_data should look, which is why I'm trying to fit the value for new_data into a type-based CASE statement.
Any tips, hints, or suggestions would be greatly appreciated.
One way is to handle the set json_array_elements() returns in a subquery.
UPDATE my_table
SET new_data = CASE
WHEN data->>'type' = 'a' THEN
(SELECT json_build_object('items',
json_agg(jae.item::jsonb - 'color'))
FROM json_array_elements(data->'items') jae(item))
END;
db<>fiddle
Also note that - isn't defined for json only for jsonb. So unless your columns are actually jsonb you need a cast. And you don't need an explicit ... ELSE NULL ... in a CASE expression, NULL is already the default value if no other value is specified in an ELSE branch.

how to extract properly when sqlite json has value as an array

I have a sqlite database and in one of the fields I have stored complete json object . I have to make some json select requests . If you see my json
the ALL key has value which is an array . We need to extract some data like all comments where "pod" field is fb . How to extract properly when sqlite json has value as an array ?
select json_extract(data,'$."json"') from datatable ; gives me entire thing . Then I do
select json_extract(data,'$."json"[0]') but i dont want to do it manually . i want to iterate .
kindly suggest some source where i can study and work on it .
MY JSON
{
"ALL": [{
"comments": "your site is awesome",
"pod": "passcode",
"originalDirectory": "case1"
},
{
"comments": "your channel is good",
"data": ["youTube"],
"pod": "library"
},
{
"comments": "you like everything",
"data": ["facebook"],
"pod": "fb"
},
{
"data": ["twitter"],
"pod": "tw",
"ALL": [{
"data": [{
"codeLevel": "3"
}],
"pod": "mo",
"pod2": "p"
}]
}
]
}
create table datatable ( path string , data json1 );
insert into datatable values("1" , json('<abovejson in a single line>'));
Simple List
Where your JSON represents a "simple" list of comments, you want something like:
select key, value
from datatable, json_each( datatable.data, '$.ALL' )
where json_extract( value, '$.pod' ) = 'fb' ;
which, using your sample data, returns:
2|{"comments":"you like everything","data":["facebook"],"pod":"fb"}
The use of json_each() returns a row for every element of the input JSON (datatable.data), starting at the path $.ALL (where $ is the top-level, and ALL is the name of your array: the path can be omitted if the top-level of the JSON object is required). In your case, this returns one row for each comment entry.
The fields of this row are documented at 4.13. The json_each() and json_tree() table-valued functions in the SQLite documentation: the two we're interested in are key (very roughly, the "row number") and value (the JSON for the current element). The latter will contain elements called comment and pod, etc..
Because we are only interested in elements where pod is equal to fb, we add a where clause, using json_extract() to get at pod (where $.pod is relative to value returned by the json_each function).
Nested List
If your JSON contains nested elements (something I didn't notice at first), then you need to use the json_tree() function instead of json_each(). Whereas the latter will only iterate over the immediate children of the node specified, json_tree() will descend recursively through all children from the node specified.
To give us some data to work with, I have augmented your test data with an extra element:
create table datatable ( path string , data json1 );
insert into datatable values("1" , json('
{
"ALL": [{
"comments": "your site is awesome",
"pod": "passcode",
"originalDirectory": "case1"
},
{
"comments": "your channel is good",
"data": ["youTube"],
"pod": "library"
},
{
"comments": "you like everything",
"data": ["facebook"],
"pod": "fb"
},
{
"data": ["twitter"],
"pod": "tw",
"ALL": [{
"data": [{
"codeLevel": "3"
}],
"pod": "mo",
"pod2": "p"
},
{
"comments": "inserted by TripeHound",
"data": ["facebook"],
"pod": "fb"
}]
}
]
}
'));
If we were to simply switch to using json_each(), then we see that a simple query (with no where clause) will return all elements of the source JSON:
select key, value
from datatable, json_tree( datatable.data, '$.ALL' ) limit 10 ;
ALL|[{"comments":"your site is awesome","pod":"passcode","originalDirectory":"case1"},{"comments":"your channel is good","data":["youTube"],"pod":"library"},{"comments":"you like everything","data":["facebook"],"pod":"fb"},{"data":["twitter"],"pod":"tw","ALL":[{"data":[{"codeLevel":"3"}],"pod":"mo","pod2":"p"},{"comments":"inserted by TripeHound","data":["facebook"],"pod":"fb"}]}]
0|{"comments":"your site is awesome","pod":"passcode","originalDirectory":"case1"}
comments|your site is awesome
pod|passcode
originalDirectory|case1
1|{"comments":"your channel is good","data":["youTube"],"pod":"library"}
comments|your channel is good
data|["youTube"]
0|youTube
pod|library
Because JSON objects are mixed in with simple values, we can no longer simply add where json_extract( value, '$.pod' ) = 'fb' because this produces errors when value does not represent an object. The simplest way around this is to look at the type values returned by json_each()/json_tree(): these will be the string object if the row represents a JSON object (see above documentation for other values).
Adding this to the where clause (and relying on "short-circuit evaluation" to prevent json_extract() being called on non-object rows), we get:
select key, value
from datatable, json_tree( datatable.data, '$.ALL' )
where type = 'object'
and json_extract( value, '$.pod' ) = 'fb' ;
which returns:
2|{"comments":"you like everything","data":["facebook"],"pod":"fb"}
1|{"comments":"inserted by TripeHound","data":["facebook"],"pod":"fb"}
If desired, we could use json_extract() to break apart the returned objects:
.mode column
.headers on
.width 30 15 5
select json_extract( value, '$.comments' ) as Comments,
json_extract( value, '$.data' ) as Data,
json_extract( value, '$.pod' ) as POD
from datatable, json_tree( datatable.data, '$.ALL' )
where type = 'object'
and json_extract( value, '$.pod' ) = 'fb' ;
Comments Data POD
------------------------------ --------------- -----
you like everything ["facebook"] fb
inserted by TripeHound ["facebook"] fb
Note: If your structure contained other objects, of different formats, it may not be sufficient to simply select for type = 'object': you may have to devise a more subtle filtering process.