How to search nested JSON in MySQL - mysql

I am using MySQL 5.7+ with the native JSON data type. Sample data:
[
{
"code": 2,
"stores": [
{
"code": 100,
"quantity": 2
},
{
"code": 200,
"quantity": 3
}
]
},
{
"code": 4,
"stores": [
{
"code": 300,
"quantity": 4
},
{
"code": 400,
"quantity": 5
}
]
}
]
Question: how do I extract an array where code = 4?
The following (working) query has the position of the data I want to extract and the search criterion hardcoded:
SELECT JSON_EXTRACT(data_column, '$[0]')
FROM json_data_table
WHERE data_column->'$[1].code' = 4
I tried using a wildcard (data_column->'$[*].code' = 4) but I get no results in return.

SELECT row FROM
(
SELECT data_column->"[*]" as row
FROM json_data_table
WHERE 4 IN JSON_EXTRACT(data_column, '$[*].code')
)
WHERE row->".code" = 4
... though this would be much easier to work with if this wasn't an unindexed array of objects at the top level. You may want to consider some adjustments to the schema.
Note:
If you have multiple rows in your data, specifying "$[i]" will pick that row, not the aggregate of it. With your dataset, "$[1].code" will always evaluate to the value of code in that single row.
Essentially, you were saying:
$ json collection
[1] second object in the collection.
.code attribute labeled "code".
... since there will only ever be one match for that query, it will always eval to 4...
WHERE 4 = 4
Alternate data structure if possible
Since the entire purpose of "code" is as a key, make it the key.
[
"code2":{
"stores": [
{
"code": 100,
"quantity": 2
},
{
"code": 200,
"quantity": 3
}
]
},
"code4": {
"stores": [
{
"code": 300,
"quantity": 4
},
{
"code": 400,
"quantity": 5
}
]
}
]
Then, all it would require would be:
SELECT datacolumn->"[code4]" as code4
FROM json_data_table

This is what you are looking for.
SELECT data_column->'$[*]' FROM json_data_table where data_column->'$[*].code' like '%4%'.
The selected data will have [] around it when selecting from an array thus data_column->'$[*].code' = 4 is not possible.

Related

Copy value from grandchildren if it exists, use null otherwise

Given the JSON:
{
"id": 1,
"coding": [{
"code": 1234,
"system": "target"
}, {
"code": 5678,
"system": "other"
}]
}
I can select the value of "code" where the "system" is "target", thus:
{id: .id} + {"code": .coding[]? | select(.system=="target").code}
To produce:
{
"id": 1,
"code": 1234
}
But if the object whose "system" value is "target" does not exist in the array, thus:
{
"id": 1,
"coding": [{
"code": 5678,
"system": "other"
}]
}
I want the following result:
{
"id": 1,
"code": null
}
However, my above jq produces an empty object. How can I achieve what I want?
The select built-in yields empty unless at least one of its inputs meets the given criteria, and empty consumes almost anything around itself. Hence the empty result.
Instead, use the first built-in for alternating between the code value from the object where system is target, and null. This also covers some other cases you didn't mention explicitly.
{ id, code: first((.coding[]? | select(.system == "target") .code), null) }
Online demo

Jsonpath - Accessing array item using expression

I am using AWS Step Functions which utilizes JSONPath for providing JSON paths. I have the following input :
{
"response": {
"isSuccess": true,
"error": "",
"body": {
"count": 2,
"fields": [
{
"fieldId": 1,
"tabId": 100,
"title": "First Name"
},
{
"fieldId": 2,
"tabId": 100,
"title": "Last Name"
}
]
}
},
"iteration": {
"totalCount": 2,
"currentCount": 0,
"step": 1
}
}
I want to query the fields array as:
$.response.body.fields[$.iteration.currentCount]
The value of currentCount is incremented by 1 as part of an iteration.
I am getting an invalid XPath exception when trying to use the above.
Can someone please advice on how to provide a dynamic property value to read array values?
As described on https://github.com/json-path/JsonPath#operators you can index an array with a number only. However, you can use a filter expression to select a specific item from the array as follows.
Assuming you have another field that denotes the index such as:
{
"index": 0,
"fieldId": 2,
"tabId": 100,
"title": "Last Name"
}
You can then do
$.response.body.fields[?(#.index==$.iteration.currentCount)]

Add new fields to nested JSON array in JSONB

I have a nested JSON structure stored in a PostgreSQL table.
Table users:
id | content [JSON]
JSON:
{
"purchases": [
{
"id": 1,
"costs": [
{
"amount": 23
},
{
"amount": 34
}
]
},
{
"id": 2,
"costs": [
{
"amount": 42
}
]
}
]
}
I would like to add a field "jsonClass": "Static" to all the objects within the costs array so I have following in the end:
{
"purchases": [
{
"id": 1,
"costs": [
{
"jsonClass": "Static",
"amount": 23
},
{
"jsonClass": "Static",
"amount": 34
}
]
},
{
"id": 2,
"costs": [
{
"jsonClass": "Static",
"amount": 42
}
]
}
]
}
I couldn't figure out how to add values to such a nested structure. Anyone knows how to achieve such thing? The only way I found was to make it a text and do string replace which is not very performant and I have a lot of such entries.
Unfortunately, due to having to change multiple sub-objects, I don't know of a better way than to deconstruct and then reconstruct the object. It gets pretty hairy.
UPDATE users
SET content=(
SELECT jsonb_agg(purchase)
FROM (
SELECT jsonb_build_object('id', pid, 'purchases', jsonb_agg(cost)) AS purchase
FROM (
SELECT pid, cost || '{"jsonClass":"static"}'::jsonb AS cost
FROM (
SELECT purchase->'id' AS pid, jsonb_array_elements(purchase->'costs') AS cost
FROM jsonb_array_elements(content::jsonb->'purchases') AS purchase
) AS Q
) AS R
GROUP BY pid
) AS S
);
Fiddle
EDIT: Sorry about all the edits, forgot to test for multiple rows. Should be good now. It might be possible to simplify it a bit more, not sure.

want to get following following JSON output

I have two tables which are as follows:
quote_glass_types
id | name
1 clear float glass
2 Tinted glass
quote_glass_type_thickness
id | quote_glass_type_id_fk | thickness
1 1 5mm
2 1 8mm
3 2 5mm
4 2 8mm
Now, I would like to join these two tables and get Json as follows:
[
{
id:1,
name: "clear float glass",
thickness:{"5mm","8mm" }
},
{
id:2
name:"tinted glass",
thickness:{"5mm","8mm"}
}
]
Till now I have done like this:
$glasssetting=\DB::table('quote_glass_types')
->join('quote_glass_type_thickness','quote_glass_type_thickness.quote_glass_type_id_fk','=','quote_glass_types.id')
->select('quote_glass_type_thickness.id','name','thickness')
->get();
return $glasssetting;
which gives JSON like:
[
{
"id": 1,
"name": "Clear Float Glasss",
"thickness": "5mm"
},
{
"id": 2,
"name": "Clear Float Glasss",
"thickness": "8mm"
},
{
"id": 3,
"name": "Tinted glass",
"thickness": "5mm"
},
{
"id": 4,
"name": "Tinted glass",
"thickness": "8mm"
}
]
How do I get the required json?
Just use Eloquent for example
return App\QuoteGlassType::with('thickness')->get();
I dont know your Modelname but thats what you are looking for.
Of course make also sure to set the correct Relationships within your Model.

VB.Net Select Nested Json With Same Name

example json (snippet taken from a valid json
"items": {
"average": 564,
"head": {
"id": 99161,
i already know how to display and select values from this i currently use
average.text = If(jResults("average") Is Nothing, "", jResults("average").ToString())
id.text =If(jResults("items")("head") Is Nothing, "", jResults("items")("head")("id").ToString())
however i am unsure how to extract the stat values from the following snippet as stat is used multiple times
"items": {
"averageItemLevel": 564,
"averageItemLevelEquipped": 564,
"head": {
"id": 99161,
"stats": [
{
"stat": 32,
"amount": 651,
"reforgedAmount": -434
},
{
"stat": 5,
"amount": 2001
},
{
"stat": 36,
"amount": 1544
},
{
"stat": 7,
"amount": 3362
},
{
"stat": 49,
"amount": 434,
"reforged": true
}
],
"armor": 2244
},
im unsure how to extract each stat and place it in a different box using the string i used before
average.text = If(jResults("average") Is Nothing, "", jResults("average").ToString())
i understand i may have to use select case but im not sure how to use my existing string to determine each stat
thanks
Looks like you'd have to use an array index on "stats", so that in your example JSON, you could get the first two values using
jResults("items")("head")("stats")(0)("stat").ToString() ' = "32"
and
jResults("items")("head")("stats")(1)("stat").ToString() ' = "5"
So if for instance you wanted to total all of the "stat" fields, you could do it like so:
Dim statTotal As Integer = 0
For i As Integer = 0 To jResults("items")("head")("stats").Count - 1
statTotal += CInt(jResults("items")("head")("stats")(i)("stat"))
Next
(Of course, in the real world you'd check to make sure each one is not Nothing and that IsNumeric() is true before just throwing a CInt() in there.)
Hope that helps!