Read MySQL JSON column array as rows - mysql

I have a table
X
- id
- name (eg: 'testing')
- array_column_name (eg: ["value1","value2"])
I want a query that will return the following output.
_____________________
| column1 | column2 |
| testing | value1 |
| testing | value2 |
|_________|_________|
Essentially what I need is to convert the array to rows.
I tried using
SELECT name, JSON_EXTRACT(#'array_column_name', '*') FROM X;
But I am not sure what to put as the second parameter of the JSON_EXTRACT since my columns contain a plain array instead array of objects.

If you are using MySQL >= 8.0.4, you can use JSON_TABLE, otherwise, to achieve what you need will be somewhat less simple:
SELECT
`X`.`name` `column1`,
`der`.`column2`
FROM
`X`,
JSON_TABLE(
`array_column_name`,
'$[*]'
COLUMNS(
`column2` VARCHAR(255) PATH '$'
)
) `der`;
See db-fiddle.

Related

How to populate a JSON path dynamically in MySQL using only SQL syntax

I'm trying to write a SELECT statement that dynamically populates a JSON path based on values from a metadata table.
I have two tables: a table of metadata describing the JSON structure, and a table of business data including each employee's name and a JSON with the described properties:
CREATE TABLE TempMetaData( Sequence INT, CustomFieldName VARCHAR(64) );
INSERT INTO TempMetaData VALUES (1,'FavoriteFruit'), (2,'FavoriteColor'), (3,'FavoriteAnimal');
CREATE TABLE TempBusinessData( EmployeeName VARCHAR(16), SpecialProperties JSON );
INSERT INTO TempBusinessData VALUES ('Ann', JSON_OBJECT('FavoriteFruit','Apple' ,'FavoriteColor','Red', 'FavoriteAnimal','Dog') ),
('Bob', JSON_OBJECT('FavoriteFruit','Orange','FavoriteColor','Blue','FavoriteAnimal','Cat') );
The following is a concrete, hard-coded example that works perfectly but doesn't meet my requirements:
SELECT EmployeeName, JSON_VALUE( SpecialProperties, '$.FavoriteFruit' ) FROM TempBusinessData;
Here is the concept of what I am trying to do:
SELECT EmployeeName, JSON_VALUE( SpecialProperties, (SELECT CONCAT('$.', (SELECT CustomFieldName FROM TempMetaData WHERE Sequence = 1) ) ) FROM TempBusinessData;
I'd appreciate any guidance on how I can replace the hard-coded path with a sub-SELECT that populates the path name from the TempMetaData table. Thanks for any help!
P.S. I found this post which seems to be related, but I didn't understand it:
How to populate a json array object dynamically?
SELECT EmployeeName, JSON_UNQUOTE(JSON_EXTRACT(SpecialProperties,
CONCAT('$.', (SELECT CustomFieldName FROM TempMetaData WHERE Sequence = 1)))) AS field1
FROM TempBusinessData
Result:
+--------------+--------+
| EmployeeName | field1 |
+--------------+--------+
| Ann | Apple |
| Bob | Orange |
+--------------+--------+
You can use an expression for the path in JSON_EXTRACT(), but not in JSON_VALUE().
https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-value says:
path is a JSON path pointing to a location in the document. This must be a string literal value.

Removing an element in an MySQL's JSON array in a column

I have a "table" named users with a column name "group_in" which stores an array like [1,2,3].
I am trying to remove an element with a value (eg. 1) for a specified row (eg id:1).
Before:
id| group_in
1 | **[1,2,3]**
2 | [1,3]
After:
id | group_in
1 | **[2,3]**
2 | [1,3]
I have tried the following:-
Update users
SET group_in = JSON_REMOVE(group_in,JSON_UNQUOTE(JSON_search(group_in, 'one', 1)))
where id = 1
but I got back is
id | group_in
1 | null
2 | [1,3]
Screenshot of my table and query result for your reference.
My table
Result gotten
Please help me if you know how to solve it
Thank you 🙏
I believe JSON_SEARCH works on strings, not sure, if it was extended to integer searches as well.
One way is to flatten the array and then recombine it while excluding the value as needed.
Query -
update users set group_in = (select new_grp from (
select id,json_arrayagg(grp) new_grp from users,
JSON_TABLE(group_in, "$[*]" COLUMNS(grp INT PATH '$')) as grp_id
where id=1
and grp<>1
group by id
)X
)
where id=1;
Refer fiddle here.
Try this,
UPDATE `channels` SET `group_in` = JSON_REMOVE(`group_in`, '$[2]') WHERE `id` = 1 ;

MYSQL group by and count on JSON column

I have an sql table with json data like this
id | int(11) Primary Key
reasons | json
On the json_data column i have arrays stored.
id| reasons
1 | ["price","inactive","small","other"]
2 | ["price","other"]
3 | ["price","inactive"]
I need to group by "reasons" and get the count
price | 3
inactive | 2
small | 1
other | 2
I have tried this;
$dealInvestors = Deal::select(
"JSON_CONCAT(decline_reasons,'$.$all_possible_reasons)",
'count(reasons) as count',
)
->groupBy('reasons')
->get();
But it doesn't work.
Can anyone help me out?
SELECT reason, COUNT(*)
FROM test
CROSS JOIN JSON_TABLE(test.reasons,
'$[*]' COLUMNS (reason VARCHAR(255) PATH '$')
) jsontable
GROUP BY reason
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=43cdbb8bfca8aa3cd3e82ccf775a577e
PS. I doubt that this query can be converted to Laravel syntax. Use raw SQL.

Query mysql json column array using AND NOT CONTAINS (mysql 5.7)

I have a table with a json column that contains an array of objects, like the following:
create table test_json (json_id int not null primary key, json_data json not null) select 1 as json_id, '[{"category":"circle"},{"category":"square", "qualifier":"def"}]' as json_data union select 2 as json_id, '[{"category":"triangle", "qualifier":"xyz"},{"category":"square"}]' as json_data;
+---------+----------------------------------------------------------------------------------------+
| json_id | json_data |
+--------------------------------------------------------------------------------------------------+
| 1 | [{"category":"circle"}, {"category":"square", "qualifier":"def"}] |
| 2 | [{"category":"triangle", "qualifier":"xyz"}, {"category":"square"}] |
+---------+----------------------------------------------------------------------------------------+
I'd like to be able to query this table to look for any rows (json_id's) that contain a json object in the array with both a "category" value of "square" and no "qualifier" property.
The sample table above is just a sample and I'm looking for a query that would work over hundreds of rows and hundreds of objects in the json array.
In MySQL 8.0, you would use JSON_TABLE() for this:
mysql> select json_id, j.* from test_json, json_table(json_data, '$[*]' columns (
category varchar(20) path '$.category',
qualifier varchar(10) path '$.qualifier')) as j
where j.category = 'square' and j.qualifier is null;
+---------+----------+-----------+
| json_id | category | qualifier |
+---------+----------+-----------+
| 2 | square | NULL |
+---------+----------+-----------+
It's not clear why you would use JSON for this at all. It would be better to store the data in the normal manner, one row per object, with category and qualifier as individual columns.
A query against normal columns is a lot simpler to write, and you can optimize the query easily with an index:
select * from mytable where category = 'square' and qualifier is null;
I found another solution using only MySQL 5.7 JSON functions:
select json_id, json_data from test_json
where json_extract(json_data,
concat(
trim(trailing '.category' from
json_unquote(json_search(json_data, 'one', 'square'))
),
'.qualifier')
) is null
This assumes the value 'square' only occurs as a value for a "category" field. This is true in your simple example, but I don't know if it will be true in your real data.
Result:
+---------+------------------------------------------------------------------------+
| json_id | json_data |
+---------+------------------------------------------------------------------------+
| 2 | [{"category": "triangle", "qualifier": "xyz"}, {"category": "square"}] |
+---------+------------------------------------------------------------------------+
I still think that it's a CodeSmell anytime you reference JSON columns in a condition in the WHERE clause. I understood your comment that this is a simplified example, but regardless of the JSON structure, if you need to do search conditions, your queries will be far easier to develop if your data is stored in conventional columns in normalized tables.
Your request is not clear. Both of your SQL records has not such properties but your JSON object has. Maybe you try to find any record that has such object. So the following is your answer:
create table test_json (json_id int not null primary key, json_data json not null) select 1 as json_id, '[{"category":"circle", "qualifier":"abc"},{"category":"square", "qualifier":"def"}]' as json_data union select 2 as json_id, '[{"category":"triangle", "qualifier":"xyz"},{"category":"square"}]' as json_data;
select * from test_json;
select * from test_json where 'square' in (JSON_EXTRACT(json_data, '$[0].category'),JSON_EXTRACT(json_data, '$[1].category'))
AND (JSON_EXTRACT(json_data, '$[0].qualifier') is NULL || JSON_EXTRACT(json_data, '$[1].qualifier') is NULL);
See Online Demo
Also see JSON Function Reference

postgres json_populate_recordset not working as expected

I have a table called slices with some simple json objects that looks like this:
id | payload | metric_name
---|---------------------------------------|------------
1 | {"a_percent":99.97,"c_percent":99.97} | metric_c
2 | {"a_percent":98.37,"c_percent":97.93} | metric_c
many records of this. I am trying to get this:
a_percent | c_percent
----------|----------
99.97 | 99.97
98.37 | 97.93
I am creating the type and using json_populate_recordset along with json_agg in the following fashion:
CREATE TYPE c_history AS(
"a_percent" NUMERIC(5, 2),
"c_percent" NUMERIC(5, 2)
);
SELECT * FROM
json_populate_recordset(
NULL :: c_history,
(
SELECT json_agg(payload::json) FROM slices
WHERE metric_name = 'metric_c'
)
);
The clause select json_agg(...) by itself produces a nice array of json objects, as expected:
[{"a_percent":99.97,"c_percent":99.97}, {"a_percent":98.37,"c_percent":97.93}]
But when I run it inside json_populate_recordset, I get Error : ERROR: must call json_populate_recordset on an array of objects.
What am I doing wrong?
This is a variant of #TimBiegeleisen's solution with the function json_populate_record() used in a from clause:
select id, r.*
from slices,
lateral json_populate_record(null::c_history, payload) r;
See rextester or SqlFiddle.
You don't need to use json_agg, since it appears you want to get the set of a_percent and c_percent values for each id in a separate record. Rather just call json_populate_recordset as follows:
SELECT id, (json_populate_record(null::c_history, payload)).* FROM slices