Mysql JSON_REMOVE Array Key and Value (MariaDB) - mysql

I have the following JSON in MariaDB/MySQL:
[{"uid": 5}, {"uid": 6}, {"uid": 7}]
user_pst_tb
------------------------------
pst_id | pst_liked_by
--------------------------
1 |[]
-------|----------------
. |[{"uid": 9}]
-------|----------------
. |[]
-------|----------------
29 |[]
-------|----------------
30 | [{"uid": 5}, {"uid": 6}, {"uid": 7}]
i want to use JSON_REMOVE or any method to remove {"uid": 6} alone on pst_id = 30, but i cannot find how to formulate the path. I thought of this:
UPDATE user_pst_tb
SET `pst_liked_by` = JSON_REMOVE(
`pst_liked_by`, JSON_UNQUOTE(
REPLACE(
JSON_SEARCH( `pst_liked_by`, 'one', '6', null, '$**.uid' )
, '.uid'
, ''
)
)
) WHERE pst_id = 30;
for some reason the MariaDB and MySQL docs does not have such examples. Any help is appreciated.
I have also tried:
UPDATE user_pst_tb SET `pst_liked_by`= JSON_REMOVE(`pst_liked_by`, JSON_UNQUOTE( JSON_SEARCH(`pst_liked_by`, 'one','{"uid": 6}') )) WHERE `pst_id` = 30;
The second query clears all the JSON data sadly
UPDATE 1 (some GOOD NEWS)
I have tried this
UPDATE user_pst_tb SET `pst_liked_by` =
JSON_REMOVE(`pst_liked_by`,JSON_UNQUOTE(JSON_search(`pst_liked_by`,
'one', '6'))) WHERE `pst_id` = 30;
Somehow working but it leaves some empty {} behind.
Example: [{"uid": 5}, {}, {"uid": 7}] any idea to remove the empty brackets will be great!!

I was assisted by #ypercubeᵀᴹ
The final query that worked is:
UPDATE nz_psts_01
SET `pst_liked_by` = JSON_REMOVE(
`pst_liked_by`, JSON_UNQUOTE(
REPLACE(
JSON_SEARCH( `pst_liked_by`, 'one', '6', null, '$**.uid' )
, '.uid'
, ''
)
)
) WHERE pst_id = 29
and JSON_SEARCH( `pst_liked_by`, 'one', '6', null, '$**.uid' ) is not null ;
Hope it can help someone

Related

Nested SELECT statements and reading in nested JSON file in SQL Server

The discussed problem has been solved partly in here:
Read in nested JSON file in SQL Server
but now the JSON file was extended with more objects of different format.
Declare #json nvarchar(max)
SELECT #json =
N'{
"Model": {
"Energy-X/A": {
"x": 1,
"y": 2,
"z": 3
},
"Energy-X/B": {
"x": 4,
"y": 5,
"z": 6
}
},
"Energy":
{
"Energy-X/A": [
[
100.123456, null
],
[
101.123456, null
]
],
"Energy-X/B": [
[
102.123456, null
],
[
103.123456, null
]
]
}
}'
select * from openjson(#json, '$.Model')
with (x [int] '$."Energy-X/A".x',
y [int] '$."Energy-X/A".y',
z [int] '$."Energy-X/A".z',
x [int] '$."Energy-X/B".x',
y [int] '$."Energy-X/B".y',
z [int] '$."Energy-X/B".z'
);
select commaDelimited.* from openjson (#json)
with (energyXA nvarchar(max) '$.Energy."Energy-X/A"' as json,
energyXB nvarchar(max) '$.Energy."Energy-X/B"' as json
) as energy
cross apply (
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[0]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[0]'))
union all
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[1]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[1]'))
) commaDelimited ([Energy-X/A], [Energy-X/B]);
The solution works and the values can be extracted but now I want to combine both SELECT statements into one query and construct a correlated subquery. The columns should appear when "Energy-X/A" and Energy-X/B" match like:
Energy-X/A
Energy-X/A
x
y
z
100.123456, null
101.123456, null
1
2
3
Energy-X/B
Energy-X/B
x
y
z
102.123456, null
103.123456, null
4
5
6
or also better output would be to sum up the values of Energy-X/A and Energy-X/B in one, separate column (using a delimiter such as semicolon):
Energy-X/A
x
y
z
100.123456, null ; 101.123456, null
1
2
3
Energy-X/B
x
y
z
102.123456, null ; 103.123456, null
1
2
3
I am grateful for any help!
Since you changed your expected results significantly, I've completely re-written your query.
Start by unpivoting the A and B values into separate rows using a (values) table and json_query.
Then read those columns using openjson.
In the case of Energy you need two levels of aggregation also, in order to get your second expected result.
select
commaDelimited.*,
model.*
from (values
(json_query(#json, '$.Model.BCS'), json_query(#json, '$.Energy."Energy-X/A"')),
(json_query(#json, '$.Model.BCSA'), json_query(#json, '$.Energy."Energy-X/B"'))
) j(model, energy)
outer apply openjson(j.model)
with (
x int,
y int,
z int
) model
outer apply (
select
Energy = string_agg(c.Energy, ' ; ')
from openjson(j.energy) energy
cross apply (
select
Energy = string_agg(isnull(Xinner.value, 'null'), ', ')
from openjson(energy.value) Xinner
) c
) commaDelimited;
db<>fiddle

MySQL JSON: How to find object from key value

I'm trying to find a way to search a JSON object and get a particular key but search on another key.
Here is an example schema:
CREATE TABLE `fields` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`label` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
`options` json DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `fields` (label, options) VALUES
(
'My Field',
'[{"text": "Grass", "value": "1"}, {"text": "Synthetic (New Type - Soft)", "value": "2"}, {"text": "Synthetic (Old Type - Hard)", "value": "3"}, {"text": "Gravel", "value": "5"}, {"text": "Clay", "value": "6"}, {"text": "Sand", "value": "7"}, {"text": "Grass/Synthetic Mix", "value": "8"}]'
);
DB Fiddle: https://www.db-fiddle.com/f/npPgVqh7fJL2JweGJ5LWXE/1
So what I would like is to select the string "Grass" from options by giving the ID 1.
But there doesn't seem to be a method to do that. I can get Grass by doing this:
select JSON_EXTRACT(`options`, '$[0].text') from `fields`;
// "Grass"
But that requires knowing the index from the array
I can partially get the index of the array like this:
select JSON_SEARCH(`options`, 'one', '1') from `fields`;
// "$[0].value"
And get the index itself through something really horrible like this:
select
REPLACE(REPLACE(JSON_SEARCH(`options`, 'one', '1'), '"$[', ''), '].value"', '')
from `fields`;
// 0
And even achieve what I want through something really horrible like this:
select
JSON_EXTRACT(`options`,CONCAT('$[',REPLACE(REPLACE(JSON_SEARCH(`options`, 'one', '1'), '"$[', ''), '].value"', ''), '].text'))
from `fields`;
// "Grass"
But there's got to be a better way right?
DB Fiddle: https://www.db-fiddle.com/f/npPgVqh7fJL2JweGJ5LWXE/1
MySQL 8.0 provides JSON_TABLE() to help with cases like this.
select field_options.* from fields cross join
json_table(fields.options,
'$[*]' columns(
text text path '$.text',
value text path '$.value'
)
) as field_options
where field_options.value = 1;
+-------+-------+
| text | value |
+-------+-------+
| Grass | 1 |
+-------+-------+
But you have to do this complex JSON_TABLE() expression every time you want to write such a query.
It would be simpler to not use JSON — instead, store data in a table with normal columns (one row per text/value pair). Then you can search for values you want in either column.
SELECT * FROM field_options WHERE value = '1';
99% of the uses of JSON in MySQL that I see on Stack Overflow would be solved easily by not using JSON.

MySQL JSON: finding value of sibling element in sub-array

I have the following (pseudo)JSON in a type JSON (LONGTEXT) column in my MariaDB 10.2
{"order":
{"otherstuff":...},
{"dates":
[
{
"typeId":2,
"date":"2019-05-21 09:00:00"
},
{
"typeId":4,
"date":"2019-05-21 10:00:00"
}
]
}
}
What I need is the order's date while I know which type I need (4).
An order can have a number of dates identified by their typeId. typeId 4 is not always in second position.
SELECT JSON_UNQUOTE(JSON_SEARCH(`json`, 'one', 4, NULL, '$.dates[*].typeId'))
// gives me: $.dates[1].typeId
My first thought now was to REPLACE typeId with date, but that complains about mixed collations.
How would I (more elegantly) reference the 'date' value here?
Also, the query is supposed to be the expression of a GENERATED column in my table. Since date id4 is not necessarily there for every order, I tried this:
SELECT IF(4 IN (JSON_EXTRACT(json, '$.dates[*].typeId')), 'yes', 'no')
// above condition evaluates to [2, 4]
I have trimmed away '[' and ']' but then it only gives me a 'yes' if 4 is first in the array (is it an array?).
So (without brackets):
[4, 7] -> yes
[2, 4] -> no
I'm assuming this doesn't get recognized as an array of values but a string. Then why does it give me 'yes' if my needle is in first position?
Instead of yes and no I obviously want to use the date and NULL.
The MySQL JSON functions are quite new to me. So maybe someone could point me in the right direction?
Try:
Option 1:
SELECT
JSON_UNQUOTE(
JSON_EXTRACT(
`json`,
REPLACE(
JSON_UNQUOTE(
JSON_SEARCH(
`json`,
'one',
4,
NULL,
'$.order.dates[*].typeId'
)
),
'typeId',
'date'
)
)
) `date`;
Option 2:
SELECT
IF(
JSON_CONTAINS(
JSON_EXTRACT(
`json`,
'$.order.dates[*].typeId'
),
4
),
'yes',
'no'
) `exists`;
See dbfiddle.

plsql code to parse JSON without using third party libraries

JSON needs to be parsed using only PL/SQL code like regular expressions to get sentiment and confidence values out of it.
Something similar to this
[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"sentiment":"neutral",
"confidence":0.3510681601407111
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]
above JSON needs to be parsed to get sentiment and confidence values out of it
The JSON_TABLE function is available starting with Oracle Database 12c Release 1 (12.1.0.2).
SET NUMWIDTH 20 --Use this if SQL*Plus/ SQL developer truncates digits.
--test data
WITH t ( json_col ) AS ( SELECT '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"sentiment":"neutral",
"confidence":0.3510681601407111
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]'
FROM dual
) --test data ends
SELECT j.*
FROM t
CROSS JOIN
JSON_TABLE ( json_col,'$[*]'
COLUMNS (
sentiment VARCHAR2(20) PATH '$.sentiment',
confidence NUMBER PATH '$.confidence'
)
)
j;
SENTIMENT CONFIDENCE
-------------------- --------------------
negative .6211975044276729
neutral .3510681601407111
positive .027734335431616075
If you really don't want to use any of the built-in JSON functions and your input does not involve any nested objects then you can use SQL with a recursive sub-query factoring clause:
Oracle Setup:
CREATE TABLE test_data ( json ) AS
SELECT '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"confidence":0.3510681601407111,
"sentiment":"neutral"
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]' FROM DUAL
Query:
WITH rsqfc ( json, obj, lvl, cnt ) AS (
SELECT json,
REGEXP_SUBSTR( json, '\{(.*?)\}', 1, 1, 'n' ),
1,
REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' )
FROM test_data
WHERE REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' ) > 1
UNION ALL
SELECT json,
REGEXP_SUBSTR( json, '\{(.*?)\}', 1, LVL + 1, 'n' ),
lvl + 1,
cnt
FROM rsqfc
WHERE lvl < cnt
)
SELECT REGEXP_SUBSTR( obj, '"sentiment":\s*"(negative|neutral|positive)"', 1, 1, 'n', 1 ) AS sentiment,
TO_NUMBER( REGEXP_SUBSTR( obj, '"confidence":\s*(\d+(\.\d*)?)', 1, 1, 'n', 1 ) ) AS confidence
FROM rsqfc
Output:
SENTIMENT | CONFIDENCE
:-------- | ------------------:
negative | .6211975044276729
neutral | .3510681601407111
positive | .027734335431616075
PL/SQL:
Or using PL/SQL:
DECLARE
json CLOB := '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"confidence":0.3510681601407111,
"sentiment":"neutral"
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]';
cnt PLS_INTEGER;
obj VARCHAR2(4000);
sentiment VARCHAR2(20);
confidence NUMBER;
BEGIN
cnt := REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' );
FOR i IN 1 .. cnt LOOP
obj := REGEXP_SUBSTR( json, '\{(.*?)\}', 1, i, 'n' );
sentiment := REGEXP_SUBSTR( obj, '"sentiment":\s*"(negative|neutral|positive)"', 1, 1, 'n', 1 );
confidence := TO_NUMBER( REGEXP_SUBSTR( obj, '"confidence":\s*(\d+(\.\d*)?)', 1, 1, 'n', 1 ) );
DBMS_OUTPUT.PUT_LINE( sentiment || ' - ' || confidence );
END LOOP;
END;
/
Output:
dbms_output:
negative - .6211975044276729
neutral - .3510681601407111
positive - .027734335431616075
db<>fiddle here

MySQL json_search on numeric values

I've got a json list of objects like that
[{
"something": "bla",
"id": 2
}, {
"something": "yes",
"id": 1
}]
My id field is always a numeric value. But when I try to find id = 2, MySQL returns NULL
select
json_search(
json_extract(
'[{"something": "bla" ,"id": 2}, {"something": "yes","id": 1}]',
"$[*].id"
),
'one',
2
) as json_search;
json_search |
------------|
|
When I use a string as value in my json id object instead of a numeric value, I got a result with Index 0.
select
json_search(
json_extract(
'[{"something": "bla" ,"id": "2"}, {"something": "yes","id": 1}]',
"$[*].id"
),
'one',
"2"
) as json_search;
json_search |
------------|
"$[0]" |
I'm using MySQL 5.7.17
##version |
-----------|
5.7.17-log |
Is numeric search in json arrays not provided in MySQL?
You can try something complicated, not intuitive and possibly with performance problems, but it's an option:
mysql> SELECT JSON_SEARCH(
-> REPLACE(
-> REPLACE(
-> REPLACE(
-> JSON_EXTRACT('[
'> {"something": "bla" ,"id": 2},
'> {"something": "yes","id": 1}
'> ]', "$[*].id"),
-> ', ', '","'),
-> '[', '["'),
-> ']', '"]'),
-> 'one', '2') `json_search`;
+-------------+
| json_search |
+-------------+
| "$[0]" |
+-------------+
1 row in set (0.00 sec)
Although the JSON_EXTRACT function was returning [2, 1] and it was a valid JSON, if you search the documentation the JSON_SEARCH function is:
JSON_SEARCH(json_doc, one_or_all, search_str[, escape_char[, path] ...])
So, as I understood you can only work with STRING values, and not with numeric values. But one solution to your problem could be to use the JSON_CONTAINS function as it returns 1 or 0 if the numeric value exists or not.
select
json_contains(
json_extract(
'[{"something": "bla" ,"id": 2}, {"something": "yes","id": 1}]',
"$[*].id"
),
"2"
) as json_contains;
The only problem is that you could not get the path to the given value within the JSON document. Hope it helped.