How to update multiple values in JSONB array Postgresql array object - sql

I have a JSONB array below
[
{"name":"test","age":"21","phone":"6589","town":"54"},
{"name":"test12","age":"67","phone":"6546","town":"54"}
]
Now I want to update town,phone,age if name is test.
How to update multiple values in JSONB array?

This query is longer, but it should clarify expanding the original column to make the replacements:
with injson as (
select '[
{"name":"test","age":"21","phone":"6589","town":"54"},
{"name":"test12","age":"67","phone":"6546","town":"54"}
]'::jsonb as jarray
), substitution as (
select '{"name": "test", "age": "22", "phone": "6590", "town": "55"}'::jsonb as jnew
), expand as (
select jsonb_array_elements(jarray) as jold
from injson
), cond_update as (
select coalesce(s.jnew, e.jold) as element
from expand e
left join substitution s
on s.jnew->>'name' = e.jold->>'name'
)
select jsonb_agg(element) as result
from cond_update;
result
--------------------------------------------------------------------------------------------------------------------------------
[{"age": "22", "name": "test", "town": "55", "phone": "6590"}, {"age": "67", "name": "test12", "town": "54", "phone": "6546"}]
(1 row)
Based on the description of your table, it should look something like this:
with substitution as (
select '{"name": "test", "age": "22", "phone": "6590", "town": "55"}'::jsonb as jnew
), expand as (
select id, jsonb_array_elements("caloriesConsumption") as jold
from "calorieTracker"
where id = 1
), cond_update as (
select id, coalesce(s.jnew, e.jold) as element
from expand e
left join substitution s
on s.jnew->>'name' = e.jold->>'name'
)
update "calorieTracker"
set "caloriesConsumption" = cu.element
from cond_update
where cond_update.id = "calorieTracker".id;

Below query will give you results which contains test word in them. After you find these you can update any values in other columns.
Working Demo
CREATE TABLE TEST2 (
INFO JSON NOT NULL
);
INSERT INTO TEST2 (info)
VALUES('[
{"name":"test","age":"21","phone":"6589","town":"54"},
{"name":"test12","age":"67","phone":"6546","town":"54"},
{"name":"dest147","age":"67","phone":"6546","town":"54"}
]');
SELECT *
FROM TEST2,
json_array_elements(info) elem
WHERE elem ->> 'name' like '%test%';

You can update them dynamically by indexing each individual element :
For age :
WITH s AS
(
SELECT ('{'||idx-1||',age}')::text[] AS path
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name'='test'
)
UPDATE tab
SET jsdata = jsonb_set(jsdata,s.path,'"15"',false)
FROM s
For town :
WITH s AS
(
SELECT ('{'||idx-1||',town}')::text[] AS path
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name'='test'
)
UPDATE tab
SET jsdata = jsonb_set(jsdata,s.path,'"55"',false)
FROM s
For phone :
WITH s AS
(
SELECT ('{'||idx-1||',phone}')::text[] AS path
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name'='test'
)
UPDATE tab
SET jsdata = jsonb_set(jsdata,s.path,'"1111"',false)
FROM s
Demo
Or directly at a time :
WITH s AS
(
SELECT ('{'||idx-1||',phone}')::text[] AS path_phone,
('{'||idx-1||',town}')::text[] AS path_town,
('{'||idx-1||',age}')::text[] AS path_age
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name'='test'
)
UPDATE tab
SET jsdata = jsonb_set(jsonb_set(jsonb_set(jsdata,
s.path_phone,
'"1111"',
false),
path_town,
'"55"',
false),
s.path_age,
'"20"',
false)
FROM s
Demo

Related

how can I make result of ARRAY_AGG() function json parsable

I have a query that selects the rows from joined table as an array using ARRAY_AGG() function.
select
entity_number,
ARRAY_AGG('{"property_id":"'||property_id||'","value":"'||value||'"}') entity_properties from entities
join entity_properties
on entities.id = entity_properties.entity_id
where entities.id in (
select entity_id from entity_properties
where value = '6258006d824a25dabdb39a79.pdf'
)
group by entities.id;
what I get is:
[
{
"entity_number":"P1718238009-1",
"entity_properties":"[
\"{\"property_id\":\"006109cd-a100-437c-a683-f13413b448e6\",\"value\":\"Rozilik berildi\"}\",
\"{\"property_id\":\"010f5e23-d66f-4414-b54b-9647afc6762b\",\"value\":\"6258006d824a25dabdb39a79.pdf\"}\",
\"{\"property_id\":\"0a01904e-1ca0-40ef-bbe1-c90eaddea3fc\",\"value\":\"6260c9e9b06e4c2cc492c470_2634467.pdf\"}\"
]"
}
]
As you can see, it is not json parsable
To parse entity_properties as array of objects I need the data in this format
[
{
"entity_number":"P1718238009-1",
"entity_properties":[
{"property_id":"006109cd-a100-437c-a683-f13413b448e6","value":"Rozilik berildi"},
{"property_id":"010f5e23-d66f-4414-b54b-9647afc6762b","value":"6258006d824a25dabdb39a79.pdf"},
{"property_id":"0a01904e-1ca0-40ef-bbe1-c90eaddea3fc","value":"6260c9e9b06e4c2cc492c470_2634467.pdf"}
]
}
]
Can I achieve what I want with ARRAY_AGG()? How?
If not, what approach should I take?
Try using json_agg and json_build_object function
like this:
select
entity_number,
json_agg(json_build_object('property_id', property_id, 'value', value)) entity_properties from entities
join entity_properties
on entities.id = entity_properties.entity_id
where entities.id in (
select entity_id from entity_properties
where value = '6258006d824a25dabdb39a79.pdf'
)
group by entities.id;
Using a simplified sample data this query provides the first step of the aggregation
with tab as (
select * from (values
(1,'a','x'),
(1,'b','y'),
(2,'c','z')
) tab(entity_number,property_id,value)
)
select
entity_number,
json_agg( json_build_object('property_id', property_id, 'value', value)) entity_properties
from tab
group by 1
;
entity_number|entity_properties |
-------------+----------------------------------------------------------------------------+
1|[{"property_id" : "a", "value" : "x"}, {"property_id" : "b", "value" : "y"}]|
2|[{"property_id" : "c", "value" : "z"}]
Additional aggregation returns the final json array
with tab as (
select * from (values
(1,'a','x'),
(1,'b','y'),
(2,'c','z')
) tab(entity_number,property_id,value)
),
tab2 as (
select
entity_number,
json_agg( json_build_object('property_id', property_id, 'value', value)) entity_properties
from tab
group by 1
)
select
json_agg(
json_build_object(
'entity_number',
entity_number,
'entity_properties',
entity_properties
)
)
from tab2
[
{
"entity_number": 1,
"entity_properties": [
{
"value": "x",
"property_id": "a"
},
{
"value": "y",
"property_id": "b"
}
]
},
{
"entity_number": 2,
"entity_properties": [
{
"value": "z",
"property_id": "c"
}
]
}
]
Note that I used jsonb_pretty to format the output.

Nested JSON object query only returns on first root

I have a nested JSON object database query that is used as input for a treeview component. This query works when there is 1 root in the database, but it breaks when having multiple roots. I can't really find a solution to it, could someone take a look?
Link to the dbfiddle: https://dbfiddle.uk/sjYamgm1
Query
CREATE OR REPLACE FUNCTION json_tree2() RETURNS jsonb AS $$
DECLARE
_json_output jsonb;
_temprow record;
BEGIN
SELECT
jsonb_build_object('id', id, 'label', "categoryName", 'children', array_to_json(ARRAY[]::uuid[]))
INTO _json_output
FROM "Category"
WHERE "parentCategory" IS NULL;
FOR _temprow IN
WITH RECURSIVE tree(id, ancestor, child, path, json) AS (
SELECT
t1.id,
NULL::uuid,
t2.id,
'{children}'::text[] || (row_number() OVER (PARTITION BY t1.id ORDER BY t2.id) - 1)::text,
jsonb_build_object('id', t2.id, 'label', t2."categoryName", 'children', array_to_json(ARRAY[]::uuid[]))
FROM "Category" t1
LEFT JOIN "Category" t2 ON t1.id = t2."parentCategory"
WHERE t1."parentCategory" IS NULL
UNION
SELECT
t1.id,
t1."parentCategory",
t2.id,
tree.path || '{children}' || (row_number() OVER (PARTITION BY t1.id ORDER BY t2.id) - 1)::text,
jsonb_build_object('id', t2.id, 'label', t2."categoryName", 'children', array_to_json(ARRAY[]::uuid[]))
FROM "Category" t1
LEFT JOIN "Category" t2 ON t1.id = t2."parentCategory"
INNER JOIN tree ON (t1.id = tree.child)
WHERE t1."parentCategory" = tree.id
)
SELECT
child as id, path, json
FROM tree
WHERE child IS NOT NULL ORDER BY path
LOOP
SELECT jsonb_insert(_json_output, _temprow.path, _temprow.json) INTO _json_output;
END LOOP;
RETURN _json_output;
END;
$$ LANGUAGE plpgsql;
SELECT jsonb_pretty(json_tree2())
Problem: I only get the first root.
{
    "id": "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
    "label": "1",
    "children": [
        {
            "id": "9dfef3df-d67b-4afd-a591-2e9b1c0b21b1",
            "label": "2.1",
            "children": [
                {
                    "id": "903a727f-d94d-44ff-b2f6-a985fd167342",
                    "label": "1.1.1",
                    "children": [
                    ]
                },
                {
                    "id": "903a727f-d94d-44ff-b2f6-a985fd167321",
                    "label": "2.1.1",
                    "children": [
                    ]
                }
            ]
        },
        {
            "id": "9dfef3df-d67b-4afd-a591-2e9b1c0b21b7",
            "label": "1.1",
            "children": [
            ]
        }
    ]
}
Expected outcome example
[
{
"id": "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
"label": "1",
"children": [
{
"id": "9dfef3df-d67b-4afd-a591-2e9b1c0b21b7",
"label": "1.1",
"children": [
{
"id": "903a727f-d94d-44ff-b2f6-a985fd167342",
"label": "1.1.1",
"children": [
]
},
]
},
{
"id": "bfa3fdf8-4672-404e-baf5-0f9098a5705e",
"label": "2",
"children": [
{
"id": "9dfef3df-d67b-4afd-a591-2e9b1c0b21b1",
"label": "2.1",
"children": [
{
"id": "903a727f-d94d-44ff-b2f6-a985fd167321",
"label": "2.1.1",
"children": [
]
},
]
}
]
Dummy data
CREATE TABLE "Category" (
id uuid,
categoryName text,
parentCategory uuid
);
INSERT INTO "Category" VALUES
('bfa3fdf8-4672-404e-baf5-0f9098a5705b', '1', NULL),
('bfa3fdf8-4672-404e-baf5-0f9098a5705e', '2', NULL),
('9dfef3df-d67b-4afd-a591-2e9b1c0b21b7', '1.1', 'bfa3fdf8-4672-404e-baf5-0f9098a5705b'),
('9dfef3df-d67b-4afd-a591-2e9b1c0b21b1', '2.1', 'bfa3fdf8-4672-404e-baf5-0f9098a5705e'),
('903a727f-d94d-44ff-b2f6-a985fd167342', '1.1.1', '9dfef3df-d67b-4afd-a591-2e9b1c0b21b7'),
('903a727f-d94d-44ff-b2f6-a985fd167321', '2.1.1', '9dfef3df-d67b-4afd-a591-2e9b1c0b21b1');
SELECT * FROM "Category";
In your recursive cte, you can build the tree from the bottom up, starting with the leaf nodes and aggregating at each iteration:
create or replace function json_tree2() returns jsonb AS $$
declare
_json_output jsonb;
_temprow record;
begin
with recursive leaf_normalized(id, cnt, p, o, r) as(
select c.id||1::text||'-NULL', 1, c.id, c.id, (select max(array_length(regexp_split_to_array(c3.categoryName, '\.'), 1)) from category c3) - array_length(regexp_split_to_array(c.categoryName, '\.'), 1) - 1
from category c
where not exists (select 1 from category c1 where c1.parentCategory = c.id) and array_length(regexp_split_to_array(c.categoryName, '\.'), 1) < (select max(array_length(regexp_split_to_array(c3.categoryName, '\.'), 1)) from category c3)
union all
select c.o||(c.cnt + 1)::text||'-NULL', c.cnt + 1, c.id, c.o, c.r - 1 from leaf_normalized c where c.r > 0
),full_normalized(id, categoryName, parentCategory) as (
select c.* from category c
union all
select c.id, '0', c.p from leaf_normalized c
),
cte(id, cname, p, js) as (
select c.id, c.categoryName, c.parentCategory, '[]'::jsonb from full_normalized c
where not exists (select 1 from full_normalized c1 where c1.parentCategory = c.id)
union all
select t.id, t.cname, t.p, jsonb_agg(t.js)
from (select c1.id, c1.categoryName cname, c1.parentCategory p,
jsonb_build_object('id', c.id, 'label', c.cname, 'children', c.js) js
from cte c join full_normalized c1 on c.p = c1.id) t
group by t.id, t.cname, t.p
),
clean(js, f) as (
select (select jsonb_agg(jsonb_build_object('id', c.id, 'label', c.cname, 'children', c.js))
from cte c where c.p is null)::text, 1
union all
select regexp_replace(c.js, '\{"id":\s"[\w\-]+\-NULL", "label"\: "\w+", "children": \[\]\}(?:,\s)*', ''), (array_length(regexp_matches(c.js, '\{"id":\s"[\w\-]+\-NULL", "label"\: "\w+", "children": \[\]\}(?:,\s)*'), 1) > 0)::int from clean c where c.f = 1
)
select (select c.js::jsonb from clean c where not exists (select 1 from regexp_matches(c.js, '\{"id":\s"[\w\-]+\-NULL", "label"\: "\w+", "children": \[\]\}(?:,\s)*')))
into _json_output;
return _json_output;
end;
$$ language plpgsql;
See fiddle.

SQL Generate JSON string using FOR JSON PATH with 2 ROOTs

I have a very simple table containing 5 columns and the table will only hold 1 record at a time. I'm to generate a JSON string from the record and send it to an endpoint.
This is how the JSON string are to be formatted. As you can see it contains 2 'roots' and this is giving me a hard time getting the correct format
{
"fields": [
{
"fieldName": "Brand",
"values": [
"FORD"
]
},
{
"fieldName": "Engine",
"values": [
"V12"
]
},
{
"fieldName": "Location",
"values": [
"Monaco"
]
}
],
"categories": [
{
"fieldName": "Colour",
"values": [
[
{
"name": "Blue"
}
]
]
},
{
"fieldName": "Interior",
"values": [
[
{
"name": "Red"
}
]
]
}
]
}
This is my table containing the 5 columns
I have managed to create 2 separate SQL queries to get the JSON string. But I can't figure out how do it in one select.
SELECT (
SELECT X.* FROM (
SELECT CASE WHEN CarName IS NOT NULL THEN 'Brand' ELSE NULL END AS fieldName,
CarName AS [value]
FROM [dbo].[JSONBODY]
UNION
SELECT CASE WHEN Engine IS NOT NULL THEN 'Engine' ELSE NULL END AS fieldName,
Engine AS [value]
FROM [dbo].[JSONBODY]
UNION
SELECT CASE WHEN [location] IS NOT NULL THEN 'Location' ELSE NULL END AS fieldName,
[Location] AS [value]
FROM [dbo].[JSONBODY] ) X
FOR JSON PATH, ROOT('fields'))
SELECT (
SELECT Y.* FROM (
SELECT CASE WHEN Colour IS NOT NULL THEN 'Colour' ELSE NULL END AS fieldName,
JSON_QUERY('[["' + Colour + '"]]') AS 'value.name'
FROM [dbo].[JSONBODY]
UNION
SELECT CASE WHEN Interior IS NOT NULL THEN 'Interior' ELSE NULL END AS fieldName,
JSON_QUERY('[["' + Interior + '"]]') AS 'value.name'
FROM [dbo].[JSONBODY]) Y
FOR JSON PATH, ROOT('categories'))
And here are the 2 JSON strings:
{"fields":[{"fieldName":"Brand","value":"Ford"},{"fieldName":"Engine","value":"V6"},{"fieldName":"Location","value":"Boston"}]}
{"categories":[{"fieldName":"Colour","value":{"name":[["Blue"]]}},{"fieldName":"Interior","value":{"name":[["Black"]]}}]}
Question 1:
Is it possible to create the JSON string through a single SQL Select? And how can I do it?
Question 2:
If a column value is NULL it is excluded automatically from the JSON string. But I had to add the fieldName to the select and had hoped it would have exclude it from the JSON string if the corresponding field was NULL. However it creates a {}, in the JSON string. And this is not accepted when calling the endpoint. So is there another way to do it when a column value is NULL? I can of course delete it from the JSON string afterwards....
Hope the above makes sense
To do it as a single SELECT you can just UNION ALL the two results together
You can unpivot the values, then check them afterwards for nulls.
Unfortunately, SQL Server does not have JSON_AGG, so you have to bodge it with STRING_AGG and STRING_ESCAPE
SELECT
v.fieldName,
value = JSON_QUERY('[' + STRING_AGG('"' + STRING_ESCAPE(v.value, 'json') + '"', ',') + ']')
FROM [dbo].[JSONBODY] jb
CROSS APPLY (VALUES
('Brand', jb.Brand),
('Engine', jb.Engine),
('Location', jb.Location)
) v(fieldName, value)
GROUP BY
v.fieldName
FOR JSON PATH, ROOT('fields');
UNION ALL
SELECT
v.fieldName,
[value.name] = JSON_QUERY('[[' + STRING_AGG('"' + STRING_ESCAPE(v.value, 'json') + '"', ',') + ']]')
FROM [dbo].[JSONBODY] jb
CROSS APPLY (VALUES
('Colour', jb.Colour),
('Interior', jb.Interior)
) v(fieldName, value)
GROUP BY
v.fieldName
FOR JSON PATH, ROOT('categories');
If you know you will only ever have one row, you can simplify it by removing the GROUP BY
SELECT (
SELECT
v.fieldName,
value = JSON_QUERY('["' + STRING_ESCAPE(v.value, 'json') + '"]')
FROM [dbo].[JSONBODY] jb
CROSS APPLY (VALUES
('Brand', jb.Brand),
('Engine', jb.Engine),
('Location', jb.Location)
) v(fieldName, value)
WHERE v.value IS NOT NULL
FOR JSON PATH, ROOT('fields')
)
UNION ALL
SELECT (
SELECT
v.fieldName,
[value.name] = JSON_QUERY('[["' + STRING_ESCAPE(v.value, 'json') + '"]]')
FROM [dbo].[JSONBODY] jb
CROSS APPLY (VALUES
('Colour', jb.Colour),
('Interior', jb.Interior)
) v(fieldName, value)
WHERE v.value IS NOT NULL
FOR JSON PATH, ROOT('categories')
);
db<>fiddle

GET last element of array in json column of my Transact SQL table

Thanks for helping.
I have my table CONVERSATIONS structured in columns like this :
[ ID , JSON_CONTENT ]
In the column ID i have a simple id in Varchar
In the column JSON_CONTENT i something like this :
{
"id_conversation" : "25bc8cbffa8b4223a2ed527e30d927bf",
"exchanges": [
{
"A" : "...",
"B": "..."
},
{
"A" : "...",
"B": "..."
},
{
"A" : "...",
"Z" : "..."
}
]
}
I would like to query and get the id and the last element of exchanges :
[ ID , LAST_ELT_IN_EXCHANGE_IN_JSON_CONTENT]
I wanted to do this :
select TOP 3 ID, JSON_QUERY(JSON_CONTENT, '$.exchange[-1]')
from CONVERSATION
But of course Transact SQL is not Python.
I saw theses answers, but i don't know how to applicate to my problem.
Select last value from Json array
Thanks for helping <3
If I understand you correctly, you need an additional APPLY operator and a combination of OPENJSON() and ROW_NUMBER(). The result from the OPENJSON() call is a table with columns key, value and type and when the JSON content is an array, the key column returns the index of the element in the specified array:
Table:
SELECT ID, JSON_CONTENT
INTO CONVERSATION
FROM (VALUES
(1, '{"id_conversation":"25bc8cbffa8b4223a2ed527e30d927bf","exchanges":[{"A":"...","B":"..."},{"A":"...","B":"..."},{"A":"...","Z":"..."}]}')
) v (ID, JSON_CONTENT)
Statement:
SELECT c.ID, j.[value]
FROM CONVERSATION c
OUTER APPLY (
SELECT [value], ROW_NUMBER() OVER (ORDER BY CONVERT(int, [key]) DESC) AS rn
FROM OPENJSON(c.JSON_CONTENT, '$.exchanges')
) j
WHERE j.rn = 1
Result:
ID value
------------------------
1 {
"A" : "...",
"Z" : "..."
}
Notice, that -1 is not a valid array index in your path expression, but you can access the item in a JSON array by index (e.g. '$.exchanges[2]').

SQL query to match and return all occurrences of search string

I have a json document in a column (record) with a table (TABLE) as below. Need to write a SQL query to bring all occurrences of values of fields "a", "b", 'k" within aaagroup.
Result should be:
NAME1 age1 comment1
NAME2 age2
NAME3 comment3
JSON data:
{
"reportfile": {
"aaa": {
"aaagroup": [{
"a": "NAME1",
"b": "age1",
"k": "comment1"
},
{
"a": "NAME2",
"b": "age2"
},
{
"a": "NAME3",
"k": "comment3"
}]
},
"dsa": {
"dsagroup": [{
"j": "Name"
},
{
"j": "Title"
}]
}
}
}
I used the below query for a single occurrence:
Data:
{"reportfile":{"aaa":{"aaagroup":[{"a":"NAME1","k":"age1}]},"dsa":{"dsagroup":[{"j":"USERNAME"}],"l":"1","m":"1"}}}
Query:
select
substr(cc.BUS_NME, 1, strpos(cc.BUS_NME,'"')-1) as BUS_NME,
substr(cc.AGE, 1, strpos(cc.AGE,'"')-1) as AGE
from
(substr(bb.aaa,strpos(bb.aaa,'"a":"')+5) as BUS_NME,
substr(bb.aaa,strpos(bb.aaa,'"k":"')+5) as AGE
from
(substr(aa.G, strpos(aa.G,'"aaagroup'),strpos(aa.G,'},')) as aaa
from
(select substr(record, strpos(record,'"aaagroup')) as G
from TABLE) aa) bb) cc
ush rani – If I am getting your question correctly, you will have a external table like this and you can try below query to get the desire result from external table
sample external table:
CREATE EXTERNAL TABLE Ext_JSON_data(
reportfile string
)
ROW FORMAT SERDE
'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
'serialization.format' = '1'
)
LOCATION
's3://bucket/folder/'
Query to fetch desire result:
WITH the_table AS (
SELECT CAST(social AS MAP(VARCHAR, JSON)) AS social_data
FROM (
VALUES
(JSON '{"aaa": {"aaagroup": [{"a": "NAME1","b": "age1","k": "comment1"},{"a": "NAME2","b": "age2"},{"a": "NAME3","k": "comment3"}]},"dsa": {"dsagroup": [{"j": "Name"},{"j": "Title"}]}}')
) AS t (social)
),
cte_first_level as
(
SELECT
first_level_key
,CAST(first_level_value AS MAP(VARCHAR, JSON))As first_level_value
FROM the_table
CROSS JOIN UNNEST (social_data) AS t (first_level_key, first_level_value)
),
cte_second_level as
(
Select
first_level_key
,SECOND_level_key
,SECOND_level_value
from
cte_first_level
CROSS JOIN UNNEST (first_level_value) AS t (SECOND_level_key, SECOND_level_value)
)
SELECT
first_level_key
,SECOND_level_key
,SECOND_level_value
,items
,items['a'] value_of_a
,items['b'] value_of_b
,items['k'] value_of_k
from
cte_second_level
cross join unnest(cast(json_extract(SECOND_level_value, '$') AS ARRAY<MAP<VARCHAR, VARCHAR>>)) t (items)
Query Output :