Appending a new array to a JSON object with SQL Server - sql

I would like to append an array to a JSON object:
JSON object:
{ "value1": "test" }
Array to be appended:
{ "array2": ["1", "2", "3"] }
Expected result:
{ "value1": "test", "array2": ["1", "2", "3"] }
My attempts with JSON_MODIFY failed:
Attempt #1:
SELECT
JSON_MODIFY('{ "value1": "test" }',
'append $.array2',
JSON_QUERY('[ "1", "2", "3" ]'))
-- { "value1": "test", "array2": [["1", "2", "3"]] }
-- An array within an array is appended
Attempt #2:
SELECT
JSON_MODIFY('{ "value1": "test" }',
'append $',
JSON_QUERY('{"array2": [ "1", "2", "3" ]}'))
-- { "value1": "test" }
-- Result doesn't contain the array at all

You are trying to set a property, not appending items to array so remove the append keyword:
SELECT JSON_MODIFY(
'{"value1": "test"}',
'$.array2',
JSON_QUERY('["1", "2", "3"]')
)
-- {"value1": "test","array2":["1", "2", "3"]}

Related

Transform JSON: select one row from array of json objects

I can't get a specific row from this JSON array.
So I want to get the object where filed 'type' is equal to 'No-Data'
Are there exist any functions in SQL to take the row or some expressions?
"metadata": { "value": "JABC" },
"force": false
"users": [
{ "id": "111", "comment": "aaa", type: "Data" },
{ "id": "222", "comment": "bbb" , type:"No-Data"},
{ "id": "333", "comment": "ccc", type:"Data" }
]
You can use a JSON path query:
select jsonb_path_query_first(the_column, '$.users[*] ? (#.type == "No-Data")')
from the_table
This assumes that the column is defined as jsonb (which it should be). If it's not you have to cast it: the_column::jsonb
Online example

Update one key value in JSON using Presto

I have a JSON column (_col0) like below and wanted to update only the 'name' part of json to new value.
{
"id":"1234",
"name":"Demo 1",
"attributes":[
{
"id": "1122",
"name": "affiliate",
"type": "number"
}
],
"behaviors": [
{
"id": "246685",
"name": "Email Send",
"scheduleOption": null,
"defaultTimeFilterEnabled": true,
"schema": []
}
]
}
I wanted to only change value of the outer "name" parameter from 'Demo 1' to 'Demo 2'. The SQL I tried does change the name parameter but makes the rest all to null.
select transform_values(cast(json_parse(_col0) as MAP(varchar, json)) , (k, v) -> if(k='name','Demo 2')) from table1
if has overload with 3 parameters, the 3rd being value for false case, use it to return the current value (you will need to transform either you varchar literal to json or json value to varchar):
-- sample data
WITH dataset (json_str) AS (
VALUES ('{
"id":"1234",
"name":"Demo 1",
"attributes":[
{
"id": "1122",
"name": "affiliate",
"type": "number"
}
],
"behaviors": [
{
"id": "246685",
"name": "Email Send",
"scheduleOption": null,
"defaultTimeFilterEnabled": true,
"schema": []
}
]
}')
)
-- query
select transform_values(
cast(json_parse(json_str) as MAP(varchar, json)),
(k, v)->if(k = 'name', cast('Demo 2' as json), v)
)
from dataset
Output:
_col0
{behaviors=[{"id":"246685","name":"Email Send","scheduleOption":null,"defaultTimeFilterEnabled":true,"schema":[]}], name="Demo 2", attributes=[{"id":"1122","name":"affiliate","type":"number"}], id="1234"}

Filtering out objects from multiple arrays in a JSONB column

I have a JSON structure with two arrays saved in a JSONB column. A bit simplified it looks like this
{
"prop1": "abc",
"prop2": "xyz",
"items": [
{
"itemId": "123",
"price": "10.00"
},
{
"itemId": "124",
"price": "9.00"
},
{
"itemId": "125",
"price": "8.00"
}
],
"groups": [
{
"groupId": "A",
"discount": "20",
"discountId": "1"
},
{
"groupId": "B",
"discount": "30",
"discountId": "2"
},
{
"groupId": "B",
"discount": "20",
"discountId": "3"
},
{
"groupId": "C",
"discount": "40",
"discountId": "4"
}
]
}
Schema:
CREATE TABLE campaign
(
id TEXT PRIMARY KEY,
data JSONB
);
Since each row (data column) can be fairly large, I'm trying to filter out matching item objects and group objects from the items and groups arrays.
My current query is this
SELECT * FROM campaign
WHERE
(data -> 'items' #> '[{"productId": "123"}]') OR
(data -> 'groups' #> '[{"groupId": "B"}]')
which returns rows containing either the matching group or the matching item. However, depending on the row, the data column can be a fairly large JSON object (there may be hundreds of objects in items and tens in groups and I've omitted several keys/properties for brevity in this example) which is affecting query performance (I've added GIN indexes on the items and groups arrays, so missing indices is not why it's slow).
How can I filter out the items and groups arrays to only contain matching elements?
Given this matching row
{
"prop1": "abc",
"prop2": "xyz",
"items": [
{
"itemId": "123",
"price": "10.00"
},
{
"itemId": "124",
"price": "9.00"
},
{
"itemId": "125",
"price": "8.00"
}
],
"groups": [
{
"groupId": "A",
"discount": "20",
"discountId": "1"
},
{
"groupId": "B",
"discount": "30",
"discountId": "2"
},
{
"groupId": "B",
"discount": "20",
"discountId": "3"
},
{
"groupId": "C",
"discount": "40",
"discountId": "4"
}
]
}
I'd like the result to be something like this (the matching item/group could be in different columns from the rest of the data column - doesn't have to be returned in a single JSON object with two arrays like this, but I would prefer it if doesn't affect performance or lead to a really hairy query):
{
"prop1": "abc",
"prop2": "xyz",
"items": [
{
"itemId": "123",
"price": "10.00"
}
],
"groups": [
{
"groupId": "B"
"discount": "20",
"discountId": "3"
}
]
}
What I've managed to do so far is unwrap and match an object in the items array using this query, which removes the 'items' array from the data column and filters out the matching item object to a separate column, but I'm struggling to join this with matches in the groups array.
SELECT data - 'items', o.obj
FROM campaign c
CROSS JOIN LATERAL jsonb_array_elements(c.data #> '{items}') o(obj)
WHERE o.obj ->> 'productId' = '124'
How can I filter both arrays in one query?
Bonus question: For the groups array I also want to return the object with the lowest discount value if possible. Or else the result would need to be an array of matching group objects instead of a single matching group.
Related questions: How to filter jsonb array elements and How to join jsonb array elements in Postgres?
If your postgres version is 12 or more, you can use the jsonpath language and functions. The query below returns the expected result with the subset of items and groups which match the given criteria. Then you can adapt this query within a sql function so that the search criteria is an input parameter.
SELECT jsonb_set(jsonb_set( data
, '{items}'
, jsonb_path_query_array(data, '$.items[*] ? (#.itemId == "123" && #.price == "10.00")'))
, '{groups}'
, jsonb_path_query_array(data, '$.groups[*] ? (#.groupId == "B" && #.discount == "20" && #.discountId == "3")'))
FROM (SELECT
'{
"prop1": "abc",
"prop2": "xyz",
"items": [
{
"itemId": "123",
"price": "10.00"
},
{
"itemId": "124",
"price": "9.00"
},
{
"itemId": "125",
"price": "8.00"
}
],
"groups": [
{
"groupId": "A",
"discount": "20",
"discountId": "1"
},
{
"groupId": "B",
"discount": "30",
"discountId": "2"
},
{
"groupId": "B",
"discount": "20",
"discountId": "3"
},
{
"groupId": "C",
"discount": "40",
"discountId": "4"
}
]
}' :: jsonb) AS d(data)
WHERE jsonb_path_exists(data, '$.items[*] ? (#.itemId == "123" && #.price == "10.00")')
AND jsonb_path_exists(data, '$.groups[*] ? (#.groupId == "B" && #.discount == "20" && #.discountId == "3")')

Comparing json in Karate

I have two jsons array responses with same data but the attributes are different. How to compare this kind of jsons.
json 1:
comments: [
{
"onetag1": "1",
"onetag2": "2"
},
{
"onetag11": "3",
"onetage12": "4"
}
]
json 2:
newcommentslist: [
{
"newtag2": "2",
"newtag1": "1"
},
{
"newtag11": "3",
"newtage12": "4"
}
]
Use JsonPath:
* def first = [ { "onetag1": "1", "onetag2": "2" }, { "onetag11": "3", "onetage12": "4" } ]
* def values = $first[*].*
* match values == ['1', '2', '3', '4']
Or transform one of them: https://stackoverflow.com/a/53120851/143475

Apache Nifi: UpdateRecord replace child values

I'm trying to use UpdateRecord 1.9.0 processor to modify a JSON but it does not replace the values as I want.
this is the source message
{
"type": "A",
"ids": [{
"id": "1",
"value": "abc"
}, {
"id": "2",
"value": "def"
}, {
"id": "3",
"value": "ghi"
}
]
}
and the wanted output
{
"ids": [{
"userId": "1",
}, {
"userId": "2",
}, {
"userId": "3",
}
]
}
I have configured the processor as follows
processor config
Reader:
reader
Schema registry:
schema
writer:
writer
And it works, the output is a JSON without the field 'type' and the ids have the field 'userId' instead 'id' and 'value'.
To fill the value of userId, I defined the replace strategy and the property to replace:
strategy
But the output is wrong. The userId is always filled with the id of the last element in the array:
{
"ids": [{
"userId": "3"
}, {
"userId": "3"
}, {
"userId": "3"
}
]
}
I think the value of the expression is ok because if I try to replace only one record it works fine (/ids[0]/userId, ..id)
Nifi docs has a really similar example (example 3):
https://nifi.apache.org/docs/nifi-docs/components/org.apache.nifi/nifi-standard-nar/1.7.1/org.apache.nifi.processors.standard.UpdateRecord/additionalDetails.html
But it does not work for me.
What am I doing wrong?
thanks
Finally I have used JoltJSONTransform processor instead UpdateRecord
JoltJSONTransform
template:
[
{
"operation": "shift",
"spec": {
"ids":{
"*":{
"id": "ids[&1].userId"
}
}
}
}
]
Easier than UpdateRecord