MySQL JSON - group by JSON array index - mysql

I'm trying to count number of nested JSON array elements grouped by parent index using MySQL 8 JSON type field. My JSON string looks like
{
"a": [
{
"b": [
1,
2,
3
]
},
{
"b": [
1
]
}
]
}
I'm trying to get the count of elements under "b" key for each "a" element. I need an output similar to:
{0: 3, 1: 1}
Meaning that a[0] has 3 elements under "b", while a[1] has 1.
This query counts total number of "b" elements across all "a"s (yields 4):
select JSON_LENGTH(json->>'$.a[*].b[*]') from myTable
Is it possible to somehow group it by a's index?
Thank you!

One option is JSON_TABLE and JSON_OBJECTAGG:
SELECT
JSON_OBJECTAGG(
`rowid` - 1,
JSON_LENGTH(`count`)
)
FROM JSON_TABLE(
'{"a":[{"b":[1,2,3]},{"b":[1]}]}',
'$.a[*]'
COLUMNS(
`rowid` FOR ORDINALITY,
`count` JSON PATH '$.b'
)
) `der`;
See db-fiddle.

Related

Use MySQL 8 json_table to dynamically extract JSON keys and nested value

I've been attempting to use MySQL 8's JSON_TABLE to extract the root keys and then their nested values. The problem is the root keys are dynamic and the nested key/value pairs might not exist.
JSON:
{
"Foo": {
"A": 3
},
"Bar": {
"A": 1,
"B": 368
},
"Biz": {
"C": 2,
"D": 10
}
}
In this JSON the root keys "Foo", "Bar", and "Biz" are dynamic and for each of their objects I want to extract the "A" key's value, which may or may not exist. For example, the above code would return this result set:
json_key
a_value
Foo
3
Bar
1
Biz
null
I've been something along these lines but no luck (just returns one row of nulls):
select * from json_table('{"Foo": {"A": 3}, "Bar": {"A": 1, "B": 368}, "Biz": {"C": 2}}',
'$' COLUMNS(
json_key varchar(255) path '$.*',
sub_value integer path '$.*.A'
)
) as i;
In the worst case I can try to restructure the JSON but it's already in the database so I'm hoping to leverage MySQL's JSON capability. Any ideas?
SELECT json_key,
JSON_EXTRACT(#json_value, CONCAT('$.', json_key, '.A')) a_value
FROM JSON_TABLE(JSON_KEYS(#json_value),
'$[*]' COLUMNS (json_key VARCHAR(255) PATH '$')) keystable
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=dd9c01e77d57206d587dd2d17340bc02

Parsing JSON Array without keys in NESTED PATH

I have a JSON like this (see the test setup below)
{
"dt" :
[
{
"values" :
[
{
"key" : "a"
},
{
"key" : "b"
}
]
}
]
}
and it is straightforeward to parse the inner array as it has keys as follows
SELECT tab.id,
jt.*
FROM parse_json_array tab,
json_table(data, '$.dt[*]'
COLUMNS (NESTED PATH '$.values[*]' COLUMNS(
key PATH '$.key' )
)) AS "JT"
where tab.id = 1;
which returns
ID, KEY
--------
1 a
1 b
But if the inner array has no keys, how could I addapt the path in NESTED PATH?
{
"dt" :
[
{
"values" :
[
"a",
"b"
]
}
]
}
All my try such as key PATH '$.*' or key PATH '*' return null or syntax error.
Note I do not need a solution, that parse both variants, but it would be of course a bonus;)
I'm on XE 18.4.0.0.0
Test data
create table parse_json_array
(id int primary key,
data CLOB constraint c1 check(data is JSON)
);
insert into parse_json_array (id, data) values (1, '{ "dt" : [ {"values" : [{"key" : "a"} , {"key" : "b" } ]} ] }');
insert into parse_json_array (id, data) values (2, '{ "dt" : [ {"values" : [ "a" , "b" ]}] }');
This will give you the id and the values within the nested array, when it's just an array of scalars rather than objects.
SELECT tab.id,
jt.*
FROM parse_json_array tab,
json_table(data, '$.dt[*].values[*]'
COLUMNS key PATH '$' )
AS "JT"
where tab.id = 2;
Storing JSON in both formats, and even more so, asking for a solution that works for both, doesn't make a lot of sense; the JSON structure is different. It's like asking for a SQL SELECT query that works for two different tables with different column sets.
If you need a solution with nested path (perhaps because you must pick out additional bits of data, which you did not share with us), you can do something like this (which is what Padders suggested in a comment):
SELECT tab.id,
jt.*
FROM parse_json_array tab,
json_table(data, '$.dt[*]' columns(
nested path '$.values[*]'
COLUMNS (key PATH '$' )) )
AS "JT"
where tab.id = 2;
EDIT:
To get values both from object members and from scalar members of the nested array, you can do something like this. Use nvl(k_token, token) if you just need the value and don't need to know if it comes from an array of objects or an array of scalars. Note that this solution will work even if you have objects and scalars mixed together in the same JSON (in the same nested array).
select p.id, j.k_token, j.token
from parse_json_array p,
json_table(data, '$.dt[*].values[*]'
columns( k_token path '$.key',
token path '$'
)
) j
;

Complex JSON using JSON_MODIFY without nested arrays or escape characters (WITHOUT_ARRAY_WRAPPER)

I am using JSON_MODIFY to build complex JSON. Moving from MySQL I am struggling with the JSON functions provided by SQL Server. The issue I'm having is that SQL Server seems to construct all JSON objects in an array. There is the WITHOUT_ARRAY_WRAPPER mechanism, which seems like it should do what I want, however; there are two undesirable consequences.
It only returns one result depending on how it is used
The result is a single string with escape characters
I have constructed a simple query which illustrates my needs and the issue.
QUERY 1
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
) AS "data";
OUTPUT 1
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
"{\"id\":1,\"name\":\"123abc\"},{\"id\":2,\"name\":\"234bcd\"}"
]
}
QUERY 2
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
OUTPUT 2
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
]
}
QUERY 1
The data object is an array (which is expected), but the problem is what is in the array... A single string with escape characters.
QUERY 2
The data object is an array, which contains an array. In order to access the actual array of data, I would use something like for each obj in data[0].... The problem this poses is, for anyone consuming the JSON object, I would have to tell them:
"In this particular object the data element is an array of
arrays--You'll want to use the first and only the first
element to access the actual array of data."
I've naively tried many different combinations of JSON_MODIFY, JSON_QUERY, and CONCAT to no avail. How can I properly use JSON_MODIFY to get the following output, without the double array in data?
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}
You are over-thinking this by trying to JSON_MODIFY an existing object.
Construct the definitions and data properties that you need, inside a subquery if necessary.
Then use FOR JSON a second time to get the outer object.
SELECT
definitions = JSON_QUERY('{"id": "INT", "name": "VARCHAR(23)"}'),
data =
(
SELECT id, name
FROM (VALUES
(1, '123abc'),
(2, '234bcd')
) v(id, name)
FOR JSON PATH
)
FOR JSON PATH;
SQL Fiddle
By trial and error, I found the solution.
Removed the append keyword from the path parameter in the JSON_MODIFY statement
Removed the WITHOUT_ARRAY_WRAPPER parameter from the FOR JSON statement.
Now the results are as expected and I don't need to explain to any consumers to "Just use data[0]"
The Query
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'$.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
Produces the following output
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}

MySQL 8 search JSON key by value in array

I've got MySQL table with JSON field, where I store data in such a format.
{
"fields": {
"1": {
"s": "y"
},
"2": {
"s": "n"
}
}
}
I need to obtain the keys in fields, e.g. 1 or 2 given the value of s.
Example query:
create table mytable ( mycol json );
insert into mytable set mycol = '{"fields": {"1": {"s": "y"},"2": {"s": "n"}}}';
select j.* from mytable, JSON_TABLE(mycol,
'$.fields.*' COLUMNS (
json_key VARCHAR(10) PATH '$',
s VARCHAR(10) PATH '$.s'
)
) AS j where j.s = 'y';
gives:
# json_key, s
null, y
I would expect to get
# json_key, s
1, y
Is it possible to get that data somehow?
I don't need the results in row / table format. I would be happy to get the comma separated list of IDs (json_keys) meeting my criterium.
EDIT:
I was also thinking about getting the paths using JSON_SEARCH and passing that to JSON_EXTRACT, this was achieved here: Combining JSON_SEARCH and JSON_EXTRACT get me: "Invalid JSON path expression."
Unfortunately the difference is that I would need to use JSON_SEARCH in all mode, as I need all results. In such a mode JSON_SEARCH returns list of paths, where as JSON_EXTRACT accepts list of arguments.
Try FOR ORDINALITY (see 12.17.6 JSON Table Functions), this type enumerates rows in the COLUMNS clause:
SELECT
JSON_UNQUOTE(
JSON_EXTRACT(
JSON_KEYS(`mycol` ->> '$.fields'),
CONCAT('$[', `j`.`row` - 1, ']')
)
) `json_key`,
`j`.`s`
FROM
`mytable`,
JSON_TABLE(
`mycol`,
'$.fields.*' COLUMNS (
`row` FOR ORDINALITY,
`s` VARCHAR(10) PATH '$.s'
)
) `j`
WHERE
`j`.`s` = 'y';
See dbfiddle.

How to get rows from table as json array list format in mySQL

I want to select rows information from table in MySQL. Rather than getting as it is, I want to get this information as JSON array list type.
For example,
A B C
=========
1 2 3
2 3 4
I want it to become data like the followings.
{ rows: [
{ "A" : 1, "B" : 2, "C" : 3},
{ "A" : 2, "B" : 3, "C" : 4}
]}
In MySQL you can achieve as below:
SELECT CONCAT('{ rows:[', result, ']}') AS result1 FROM
(
SELECT GROUP_CONCAT('{', jsondata, '}' SEPARATOR ',') AS result FROM
(
SELECT
CONCAT
(
'"A":' , A , ','
'"B":', B, ','
'"C":' , C
) AS jsondata
FROM test
) AS jsondata
) AS jsonsdata1;
SQL Demo link: http://sqlfiddle.com/#!9/95bbbc/2