Delete value from nested json - postgres - json

I have the json block modeled below. I want to selectively delete individual blocks from my_items based on the id which is AAA and BBB in my sample. ie if I tried to delete the AAA block under my_items I would want tojust delete the {"id" : "AAA"} but if wanted to delete the BBB block it would delete the larger {"name" : "TestRZ", "id" : "BBB", "description" : ""} block.
I know I can use the #- to remove whole blocks like SELECT '{sample_json}'::jsonb #- '{my_items}' would purge out the whole my_items block. But I dont know how to use this to conditionally delete children under a parent block of json. I have also used code similar to this example to append data inside a nested structure by reading in the node of the nested structure cat-ing new data to it and rewriting it. UPDATE data SET value= jsonb_set(value, '{my_items}', value->'items' || (:'json_to_adds'), true) where id='testnofeed'.
But I dont know how to apply either of these methods to: 1)Delete data in nested structure using #- or 2)Do the same using `jsonb_set. Anyone have any guidance for how to do this using either of these(or another method).
{
"urlName" : "testurl",
"countryside" : "",
"description" : "",
"my_items" : [
{
"id" : "AAA"
},
{
"name" : "TestRZ",
"id" : "BBB",
"description" : ""
},
],
"name" : "TheName"
}
Data is stored in value jsonb. when I update I will be able to pass in a unique kind so that it only updates this json in one row in db.
-- Table Definition
CREATE TABLE "public"."data" (
"id" varchar(100) NOT NULL,
"kind" varchar(100) NOT NULL,
"revision" int4 NOT NULL,
"value" jsonb
);

This works in PostgreSQL 12 and later with jsonpath support. If you do not have jsonpath, then please leave a comment.
with data as (
select '{
"urlName" : "testurl",
"countryside" : "",
"description" : "",
"my_items" : [
{
"id" : "AAA"
},
{
"name" : "TestRZ",
"id" : "BBB",
"description" : ""
}
],
"name" : "TheName"
}'::jsonb as stuff
)
select jsonb_set(stuff, '{my_items}',
jsonb_path_query_array(stuff->'my_items', '$ ? (#."id" <> "AAA")'))
from data;
jsonb_set
---------------------------------------------------------------------------------------------------------------------------------------------------
{"name": "TheName", "urlName": "testurl", "my_items": [{"id": "BBB", "name": "TestRZ", "description": ""}], "countryside": "", "description": ""}
(1 row)
To update the table directly, the statement would be:
update data
set value = jsonb_set(value, '{my_items}',
jsonb_path_query_array(value->'my_items',
'$ ? (#."id" <> "AAA")'));
This works for versions before PostgreSQL 12:
with data as (
select 1 as id, '{
"urlName" : "testurl",
"countryside" : "",
"description" : "",
"my_items" : [
{
"id" : "AAA"
},
{
"name" : "TestRZ",
"id" : "BBB",
"description" : ""
}
],
"name" : "TheName"
}'::jsonb as stuff
), expand as (
select d.id, d.stuff, e.item, e.rn
from data d
cross join lateral jsonb_array_elements(stuff->'my_items') with ordinality as e(item, rn)
)
select id, jsonb_set(stuff, '{my_items}', jsonb_agg(item order by rn)) as new_stuff
from expand
where item->>'id' != 'AAA'
group by id, stuff;
id | new_stuff
----+---------------------------------------------------------------------------------------------------------------------------------------------------
1 | {"name": "TheName", "urlName": "testurl", "my_items": [{"id": "BBB", "name": "TestRZ", "description": ""}], "countryside": "", "description": ""}
(1 row)
The direct update for this is a little more involved:
with expand as (
select d.id, d.value, e.item, e.rn
from data d
cross join lateral jsonb_array_elements(value->'my_items')
with ordinality as e(item, rn)
), agg as (
select id, jsonb_set(value, '{my_items}', jsonb_agg(item order by rn)) as new_value
from expand
where item->>'id' != 'AAA'
group by id, value
)
update data
set value = agg.new_value
from agg
where agg.id = data.id;

Related

Get nested JSON values in PL/SQL

I am trying to get nested field's values from json which is returned by function apex_web_service.make_rest_request.
DECLARE
v_address_json clob;
v_address_response clob;
l_object_address json_object_t;
BEGIN
SELECT
json_object ('Addresses' value json_object(
'AddName' value ('HQ') ,
'AddLine1' value tab1.ADDRESS_LINE1 ,
.
.
.
.
into
v_address_json
FROM
tab1 t1,
tab2 t2
WHERE
.....
.....;
v_address_response := apex_web_service.make_rest_request
(
p_url => 'https://...../addresses',
p_http_method => 'POST',
p_body => v_address_json
);
DBMS_OUTPUT.PUT_LINE('v_address_response : '||v_address_response);
At this point I am getting below in v_address_response.
{
"Metadata" : {
"application" : "",
"applicationRefId" : ""
},
"APIResponse" : {
"Status" : "SUCCESS",
"Error" : {
"Code" : "",
"Message" : "",
"Detail" : ""
}
},
"ven" : {
"Id" : 12345,
"Addresses" : [ {
"vAddId" : 1122334455,
"vAddName" : "HQ",
"AddLine1" : "1/A2, ABC, XYZ ROAD, IN",
"City" : "JKL",
"State" : "AB",
"PCode" : "102030",
"Country" : "IN",
"TaxReg" : [ {
"RegId" : 998877,
"EffectiveFrom" : "2029-11-13"
} ],
"TaxRep" : [ {
"TaxRepId" : 665544,
"EffectiveFrom" : "2022-01-01"
} ]
} ]
}
}
further I am trying to get field's value as below.
l_object_address := json_object_t.parse(v_address_response);
if l_object_address.get_Object('APIResponse').get_string('Status') = 'SUCCESS'
then
DBMS_OUTPUT.PUT_LINE(' New Address ID : '||l_object_address.get_Object('Addresses').get_string('vAddId')); --output xx
else
DBMS_OUTPUT.PUT_LINE('Error in creating address');
end if;
exception when others then
null;
end;
in the above section I am able to get value for APIResponse--> Status i.e 'SUCCESS' but unable to get value of vAddId, RegId and other nested fields. At comment, output xx, nothing is getting printed.
If you're just after reading values out of the JSON (and not intend to traverse the "object tree" up and down") I would use the JSON_TABLE SQL Function, as follows:
BEGIN
for i in (
select status,
vAddId,
vAddName,
RegId,
EffectiveFrom
-- , other columns go here
from json_table(
v_address_response,
'$'
columns(
status varchar2(255) path '$.APIResponse.Status',
-- other columns on this level go here
ven_id number path '$.ven.Id',
nested path '$.ven.Addresses' columns(
vAddId number path '$.vAddId',
vAddName varchar2(255) path '$.vAddName',
-- other columns on this level go here
nested path '$.TaxReg' columns(
RegId number path '$.RegId',
EffectiveFrom varchar2(255) path '$.EffectiveFrom'
)
)
)
)
)
loop
-- process the data here
if i.status = 'SUCCESS' then
DBMS_OUTPUT.PUT_LINE(' New Address ID : '|| i.vAddId);
else
DBMS_OUTPUT.PUT_LINE('Error in creating address');
end if;
end loop;
end;
/
fiddle
You need to descend into the ven object first and then Addresses is an array of objects, and not an object, so you need to find the zero-indexed element of the array and then get the attribute of the object. To do that, you can use:
DECLARE
v_address_response CLOB := '{
"Metadata" : {
"application" : "",
"applicationRefId" : ""
},
"APIResponse" : {
"Status" : "SUCCESS",
"Error" : {
"Code" : "",
"Message" : "",
"Detail" : ""
}
},
"ven" : {
"Id" : 12345,
"Addresses" : [ {
"vAddId" : 1122334455,
"vAddName" : "HQ",
"AddLine1" : "1/A2, ABC, XYZ ROAD, IN",
"City" : "JKL",
"State" : "AB",
"PCode" : "102030",
"Country" : "IN",
"TaxReg" : [ {
"RegId" : 998877,
"EffectiveFrom" : "2029-11-13"
} ],
"TaxRep" : [ {
"TaxRepId" : 665544,
"EffectiveFrom" : "2022-01-01"
} ]
} ]
}
}';
l_object JSON_OBJECT_T;
l_address JSON_OBJECT_T;
BEGIN
l_object := json_object_t.parse(v_address_response);
if l_object.get_Object('APIResponse').get_string('Status') = 'SUCCESS' then
l_address := TREAT( l_object.get_Object('ven').get_Array('Addresses').get(0) AS JSON_OBJECT_T );
DBMS_OUTPUT.PUT_LINE(' New Address ID : '||l_address.get_Number('vAddId')); --output xx
else
DBMS_OUTPUT.PUT_LINE('Error in creating address');
end if;
end;
/
Which outputs:
New Address ID : 1122334455
fiddle

Use of Lateral Flatten for JSON in Snowflake

I have the following JSON as a variant field in snowflake. I get NULLS when querying the data as shown below - -
create or replace table car_sales
(
src variant
)
as
select parse_json(column1) as src
from values
('{
"MyData": {
"DataID": "16784",
"EmpFirstName": "Jenny",
"EmpLastName": "Test1",
"Description": "My Company Data",
"Assignment": [
{
"AssignentId": "1077",
"AssignentName": "Multi Location",
"AssignentDescription": "usa",
},
],
"salary": 21557
},
"Update": true
}') v;
When I query the data I get Nulls -
select value:AssignentId::string as "1ProductName"
from car_sales,
lateral flatten(input => src:Assignment);
Output is NULL
Can you help to troubleshoot why this is happening?
Try adding the MyData qualifier in the lateral flatten:
with car_sales(src) as (
select
parse_json(column1) as src
from
values
(
'{ "MyData": {
"DataID": "16784",
"EmpFirstName": "Jenny",
"EmpLastName": "Test1",
"Description": "My Company Data",
"Assignment": [
{
"AssignentId": "1077",
"AssignentName": "Multi Location",
"AssignentDescription": "usa",
},
],
"salary": 21557
}, "Update": true }'
)
)
select
value:AssignentId::string as "1ProductName"
from
car_sales,
lateral flatten(input => src:MyData:Assignment);
1ProductName
1077
You are trying to access the child node directly. Specify the parent node as well
select value:AssignentId::string as "1ProductName"
from car_sales,
lateral flatten(input => src:MyData:Assignment);
input => src:Assignment
should be
input => src:MyData:Assignment
Here is the query to read values for each key
select src:MyData.DataID::string as "DataID"
, src:MyData.EmpFirstName::string as "EmpFirstName"
, src:MyData.EmpLastName::string as "EmpLastName"
, src:MyData.Description::string as "Description"
, value:AssignentId::string as "AssignentId/1ProductName"
, value:AssignentName::string as "AssignentName"
, value:AssignentDescription::string as "AssignentDescription"
, src:MyData.salary::number as "salary"
, src:Update::boolean as "Update"
from car_sales,
lateral flatten(input => src:MyData:Assignment);

Subset a JSON Object with MySQL Query

I have a MySQL database and one of the tables is called 'my_table'. In this table, one of the columns is called 'my_json_column' and this column is stored as a JSON object in MySQL. The JSON object has about 17 key:value pairs (see below). I simply want to return a "slimmed-down" JSON Object from a MySQL query that returns 4 of the 17 fields.
I have tried many different MySQL queries, see below, but I can't seem to get a returned subset JSON Object. I am sure it is simple, but I have been unsuccessful.
Something like this:
SELECT
json_extract(my_json_column, '$.X'),
json_extract(my_json_column, '$.Y'),
json_extract(my_json_column, '$.KB'),
json_extract(my_json_column, '$.Name')
FROM my_table;
yields:
5990.510000 90313.550000 5990.510000 "Operator 1"
I want to get this result instead (a returned JSON Object) with key value pairs:
[ { X: 5990.510000, Y: 90313.550, KB: 2105, Name: "Well 1" } ]
Sample data:
{
"Comment" : "No Comment",
"Country" : "USA",
"County" : "County 1",
"Field" : "Field 1",
"GroundElevation" : "5400",
"Identifier" : "11435358700000",
"Interpreter" : "Interpreter 1",
"KB" : 2105,
"Name" : "Well 1",
"Operator" : "Operator 1",
"Owner" : "me",
"SpudDate" : "NA",
"State" : "MI",
"Status" : "ACTIVE",
"TotalDepth" : 5678,
"X" : 5990.510000,
"Y" : 90313.550
}
Thank you in advance.
Use JSON_OBJECT(), available since MySQL 5.6:
Evaluates a (possibly empty) list of key-value pairs and returns a JSON object containing those pairs
SELECT
JSON_OBJECT(
'X', json_extract(my_json_column, '$.X'),
'Y', json_extract(my_json_column, '$.Y'),
'KB', json_extract(my_json_column, '$.KB'),
'Name', json_extract(my_json_column, '$.Name')
) my_new_json
FROM my_table;
This demo on DB Fiddle with your sample data returns:
| my_new_json |
| ----------------------------------------------------------- |
| {"X": 5990.51, "Y": 90313.55, "KB": 2105, "Name": "Well 1"} |

How to remove null attributes from my JSON in MySQL

I am having a table which is storing the JSON values. Within these JSONs, the JSON is having null attributes like below :
{
"name" : "AAAA",
"department" : "BBBB",
"countryCode" : null,
"languageCode" : null,
"region" : "AP"
}
I would like to write a query so that all the null attributes are removed from the output. For e.g. for the above-mentioned JSON, the resultant output JSON should be like this.
{
"name" : "AAAA",
"department" : "BBBB",
"region" : "AP"
}
I would like to have a generic query which I can apply to any JSON to get rid of null attributes in MySQL (v5.7).
In case you don't know all the keys in advance:
WITH j AS (SELECT CAST('{"a": 1, "b": "null", "c": null}' AS JSON) o)
SELECT j.o, (SELECT JSON_OBJECTAGG(k, JSON_EXTRACT(j.o, CONCAT('$."', jt.k, '"')))
FROM JSON_TABLE(JSON_KEYS(o), '$[*]' COLUMNS (k VARCHAR(200) PATH '$')) jt
WHERE JSON_EXTRACT(j.o, CONCAT('$."', jt.k, '"')) != CAST('null' AS JSON)) removed
FROM j;
Outputs:
o
removed
{"a": 1, "b": "null", "c": null}
{"a": 1, "b": "null"}
And this will keep your keys with string value "null", which is different from json null.
The following query will work for removing a single key value pair, where the value is NULL:
SELECT JSON_REMOVE(col, '$.countryCode')
FROM yourTable
WHERE CAST(col->"$.countryCode" AS CHAR(50)) = 'null';
But, I don't see a clean way of doing multiple removals in a single update. We could try to chain the updates together, but that would be ugly and non readable.
Also, to check for your JSON null, I had to cast the value to text first.
Demo
How you can remove null keys using JSON_REMOVE function. $.dummy is used if the condition is false.
select json_remove(abc,
case when json_unquote(abc->'$.name') = 'null' then '$.name' else '$.dummy' end,
case when json_unquote(abc->'$.department') = 'null' then '$.department' else '$.dummy' end,
case when json_unquote(abc->'$.countryCode') = 'null' then '$.countryCode' else '$.dummy' end,
case when json_unquote(abc->'$.languageCode') = 'null' then '$.languageCode' else '$.dummy' end,
case when json_unquote(abc->'$.region') = 'null' then '$.region' else '$.dummy' end)
from (
select cast('{
"name" : "AAAA",
"department" : "BBBB",
"countryCode" : null,
"languageCode" : null,
"region" : "AP"
}' as json) as abc ) a
Output:
{"name": "AAAA", "region": "AP", "department": "BBBB"}

Separate single record into two rows by column names

I want to separate a single record into 2 records by their column names.
Consider only a single record for now.
Currently what I get using simple select query:
{ "id" : "1", "route_name" : "6", "start_up" : "Mumbai", "destination_up" : "Delhi", "start_down" : "Delhi", "destination_down" : "Mumbai" }
What I actually need:
{ "id" : "1", "route_name" : "6", "start_up" : "Mumbai", "destination_up" : "Delhi" }, { "id" : "1", "route_name" : "6", "start_down" : "Delhi", "destination_down" : "Mumbai" }
How can I achieve this using a single query?
you can use an union
select id, route_name, start_up, destination_up
from my_table
where id ='1'
union
select id, route_name, start_down, destination_down
from my_table
where id ='1'