Search MySQL JSON field on any index - mysql

I have a specific json structure
{
"id:":"123456",
"elements":[
{
"name":"test1",
"txt":"random1"
},
{
"name":"test2",
"txt":"random2"
},
{
"name":"test3",
"tx3":"random3"
},
etc...
]
}
This is my current query that checks only on specific index:
select column->>'$.id' as m FROM table WHERE column->>'$.elements[0].name' = 'test1'
I would like to check whether any of the elements has the name i am looking for. Something like elements[*].name = 'test1'
Additional:
Is there an option to return all elements text, where element has specific name?
For example:
select <elements:txt> where <elements:name> = 'test1'

You can use JSON_SEARCH, checking to see if it returns NULL (no match) when looking for the string in each of the element names:
SELECT column->>'$.id' AS m
FROM table
WHERE JSON_SEARCH(column, 'one', 'test1', NULL, '$.elements.*.name') IS NOT NULL
If elements is actually an array, you would change the query to:
SELECT column->>'$.id' AS m
FROM table
WHERE JSON_SEARCH(column, 'one', 'test1', NULL, '$.elements[*].name') IS NOT NULL
Demo on dbfiddle
To extract the matching txt value requires hacking the path returned from JSON_SEARCH, replacing the .name part with .txt and then passing that to JSON_EXTRACT:
SELECT JSON_EXTRACT(j, REPLACE(JSON_UNQUOTE(JSON_SEARCH(j, 'one', 'test2', NULL, '$.elements[*].name')), '.name', '.txt')) AS txt
FROM test
WHERE JSON_SEARCH(j, 'one', 'test2', NULL, '$.elements[*].name') IS NOT NULL
Demo on dbfiddle

Related

How to retain an empty JSON parent object for NULL child values - SQL Server FOR JSON PATH

I am asking and answering my own question but I would like to see if anyone else has any better idea of how to do this.
I have some JSON that I am sending to a 3rd party API. I have created a fake simplified representation of what I am trying to do.
There is a primary_selection with its child elements and a secondary_selection with its child elements. If the secondary selection doesn't exist, the child elements are all NULL. According to the API the secondary_selection is required but can be empty. They also want NULL elements to be excluded.
DECLARE #JSON_WITHOUT_NULLS NVARCHAR(500)
SELECT #JSON_WITHOUT_NULLS = (
SELECT '123456' [administrative_info.account_num],
'1' [administrative_info.user_id],
'whole wheat bread' [primary_selection.vehicle_for_sauce],
'avocado' [primary_selection.topping],
'mayo' [primary_selection.sauce_type],
NULL [secondary_selection.vehicle_for_mayo],
NULL [secondary_selection.topping],
NULL [secondary_selection.sauce_type]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
SELECT #JSON_WITHOUT_NULLS
The SQL above produces the following:
"administrative_info": {
account_num": "123456",
"user_id": "1"
},
"primary_selection": {
"vehicle_for_sauce": "whole wheat bread",
"topping": "avocado",
"sauce_type": "mayo"
}
But what I want is:
"administrative_info": {
"account_num": "123456",
"user_id": "1"
},
"primary_selection": {
"vehicle_for_sauce": "whole wheat bread",
"topping": "avocado",
"sauce_type": "mayo"
},
"secondary_selection": {}
Another possible option is the statement below. You need to generate each first-level JSON object using FOR JSON PATH.
Note, that you need a JSON_QUERY() call to prevent escaping of the special characters. This is explained in the documentation: JSON_QUERY returns a valid JSON fragment. As a result, FOR JSON doesn't escape special characters in the JSON_QUERY return value. If you're returning results with FOR JSON, and you're including data that's already in JSON format (in a column or as the result of an expression), wrap the JSON data with JSON_QUERY without the path parameter.
DECLARE #JSON_WITHOUT_NULLS NVARCHAR(max)
SELECT #JSON_WITHOUT_NULLS = (
SELECT
administrative_info = JSON_QUERY((
SELECT '123456' [account_num], '1' [user_id]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)),
primary_selection = JSON_QUERY((
SELECT 'whole wheat bread' [vehicle_for_sauce], 'avocado' [topping], 'mayo' [sauce_type]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)),
secondary_selection = JSON_QUERY((
SELECT NULL [vehicle_for_mayo], NULL [topping], NULL [sauce_type]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
Result:
{
"administrative_info":{
"account_num":"123456",
"user_id":"1"
},
"primary_selection":{
"vehicle_for_sauce":"whole wheat bread",
"topping":"avocado",
"sauce_type":"mayo"
},
"secondary_selection":{
}
}
So, here is my solution, but want to see if anyone else has any other knowledge on how to do this.
DECLARE #JSON_WITHOUT_NULLS NVARCHAR(500)
SELECT #JSON_WITHOUT_NULLS = (SELECT '123456' [administrative_info.account_num],
'1' [administrative_info.user_id],
'whole wheat bread' [primary_selection.vehicle_for_sauce],
'avocado' [primary_selection.topping],
'mayo' [primary_selection.sauce_type],
NULL [secondary_selection.vehicle_for_mayo],
NULL [secondary_selection.topping],
NULL [secondary_selection.sauce_type]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
SELECT #JSON_WITHOUT_NULLS
DECLARE #EMPTY_OBJECT NVARCHAR(2) = '{}'
SELECT #JSON_WITHOUT_NULLS = JSON_MODIFY(#JSON_WITHOUT_NULLS,'$.secondary_selection',JSON_QUERY(#EMPTY_OBJECT))
SELECT #JSON_WITHOUT_NULLS

How to query MySQL JSON column that contains an array of objects

I have a MySQL (v5.7) database that has a table called deals which contains a JSON column called status.
Status contains data (in deals id = 29) in the following format (apologies for the formatting):
{
"trackStatus":
[
{
"date":[],
"notes":[],
"value":"0",
"isChecked":false
},
{
"date":["2019-03-25T17:42:45-04:00"],
"notes":[],
"value":4,
"isChecked":true
}
],
"nextStatusIndex":0,
"currentDealStatus":4
}
I am trying to query the trackStatus array where value = 4to find out if itisChecked`.
My understanding from reading the MySQL reference that I need to use the JSON_EXTRACT function to get the data.
In following some of the examples, I tested the query as follows to see if I could return trackStatus:
SELECT JSON_EXTRACT(status, '$.trackStatus')
FROM deals
WHERE deals.id = 29
This works and returns trackStatus. However, when I try to expand on this to query within trackStatus specifically for isChecked, it does not return a value (not null but blank).
SELECT JSON_EXTRACT(status, '$.trackStatus', '$.isChecked')
FROM deals
WHERE deals.id = 29 AND JSON_EXTRACT(status, '$.trackStatus', '$.value') = 4
I have tried a myriad of different queries to the point where I am going in circles.
I realize the issue is with trackStatus because if I remove that array, I can query nextStatusIndex and it works. However with the array there, it does not.
So I am hoping someone can show me how/if I can query this JSON using MySQL queries (both within trackStatus and nextStatusIndex) given the way the data is formatted.
In MySQL 8.0, it would have been possble to turn the inner JSON array to a recordset with function JSON_TABLE(), and then inspect it.
Here is a solution for version 5.7, which relies on JSON_SEARCH() and JSON_EXTRACT().
This query will give you the value of attribute isChecked for the (first) element in the trackStatus array that has attribute value set to 4:
SELECT JSON_EXTRACT(
status,
REPLACE(
REPLACE(
JSON_SEARCH(status, 'all', '4', '', '$.trackStatus[*].value'),
'"', ''),
'.value', '.isChecked')
) isCheckedAtValue4
FROM deals WHERE deals.id = 29;
With your sample data in this DB fiddle, this returns:
| isCheckedAtValue4 |
| ----------------- |
| true |
Details
You can use JSON_SEARCH to find the path to the (first) element in array trackStatus that has attribute value set to 4:
SELECT JSON_SEARCH(status, 'one', '4', '', '$.trackStatus[*].value')
FROM deals
WHERE deals.id = 29;
With your test data, this would typically return: "$.trackStatus[1].value".
We can manipulate the path string point it towards attribute isChecked of the same array element. Surrounding double quotes also need to be removed:
SELECT REPLACE(
REPLACE(
JSON_SEARCH(status, 'one', '4', '', '$.trackStatus[*].value'),
'"', ''),
'.value', '.isChecked'),
FROM deals WHERE deals.id = 29;
This returns: $.trackStatus[1].isChecked.
Finally, this expression can be be given as an argument to JSON_EXTRACT().

Query on jsonb array

I have this kind of json array and i want to check stringValue inside value array is null or not plus i want to check it with its id and fielddata is column name
[
{
"name": "50a5613e97e04cb5b8d32afa8a9975d1",
"value": {
"stringValue": null
}
},
{
"name": "bb127e8284c84692aa217539c4312394",
"value": {
"dateValue": 1549065600
}
}
]
query is:
select *
from field
WHERE (fielddata->>'name') = '50a5613e97e04cb5b8d32afa8a9975d1'
AND fielddata->'value'->>'stringValue' IS NOT NULL;
and I want use this query in laravel5.7
Try this
$result = \DB::table('field')->where('name', "50a5613e97e04cb5b8d32afa8a9975d1" )->where('value', $stringValue)->get();
if(isset($result)){
foreach($result as $res){
if(isset($res['value']->stringValue)){
// not null case
}else{
// null case
}
}
}
Within a SQL query, I think you want something like this:
select t.*
from the_table t
where exists (select *
from jsonb_array_elements(t.fielddata) as j(e)
where e ->> 'name' = '50a5613e97e04cb5b8d32afa8a9975d1'
and e -> 'value' ->> 'stringValue' is not null);
The exists sub-query will check every array element and see if at least one element has the specified name and a non-null stringValue. The query will then return the complete row from the table that fulfills the condition.
Online example: https://rextester.com/AGGJNR88809

MySQL JSON - SELECT WHERE

I'm trying to SELECT objects base on the roles property values.
Example: Select all names where role is 1 //response would return danny
Query Statement:
SELECT JSON_EXTRACT(username,'$[*].name') FROM objects WHERE JSON_CONTAINS(username,'1','$[*].roles')
COLUMN: username (JSON)
[
{
"name":"jordan",
"roles":[1,2,5]
},
{
"name":"danny",
"roles":[1,4]
}
]
Question: Why isn't my statement returning just the first object containing the name danny?
Try the following:
SELECT *
FROM objects
WHERE JSON_CONTAINS(components, '1', '$.roles');

CActiveRecord Find By Attributes Where Field Is Null

In Yii 1.1.*, how to find all data (via CActiveRecord implementation) where an attributes is NULL, kinda like:
Foo::model()->findAllByAttributes(['bar' => 'baz', 'qux' => [null, '']]);
It DOES NOT work because it produces query: WHERE (bar = 'baz') AND (qux IN (, ''))
I want to find all Foo records where:
"bar" field equals with "baz"
AND qux field IS NULL or equals with empty string
I can do it with findAll, but how about if I want to use findAllByAttributes method? Thanks.
Something like:
$cityModel = Cities::model()->findAllByAttributes(array("citny_name"=>"d"), "state_id IS NULL OR state_id = ''");
The executed query:
SELECT * FROM `cities` `t` WHERE `t`.`citny_name`=:yp0 AND
(state_id IS NULL OR state_id = ''). Bound with :yp0='d'
You can pass the condition for quz as an additional parameter into findAllByAttributes:
Foo::model()->findAllByAttributes(['bar' => 'baz'], "quz IS NULL OR quz = ''")
You can't use IN with null values unless you replace CDbCommandBuilder with your own implementation. CActiveRecord::findAllByAttributes calls CDbCommandBuilder::createColumnCriteria which in turn calls CDbCommandBuilder::createInCondition if the column values are an array.
From the source code values are cast into the column type and quoted afterwards and passed through implode resulting in the null being treated as php's null not mysql's null:
foreach($values as &$value)
{
$value=$column->typecast($value);
if(is_string($value))
$value=$db->quoteValue($value);
}
...
return $prefix.$column->rawName.' IN ('.implode(', ',$values).')';