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.
Related
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.
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.
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.
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.
I need to select SUM of all value which is realized (=true) from the mysql data set below. In this example, I should get 1000 only.
Please note that the key is dynamic (year\month\day).
I am able to get a list of value by running the sql below:
SELECT (JSON_EXTRACT(json_value, "$**.value")) AS total FROM milestone
However, i can't seem to get the filter part works with this:
JSON_CONTAINS_PATH(json_value, 'all', "$**.realized") IS NOT NULL
id=1, column json_value
{
"2018": {
"5": {
"4": {
"value": "5000"
}
},
"12": {
"4": {
"value": "80000",
"realized": "false"
}
}
}
}
id=2, column json_value
{
"2016": {
"12": {
"4": {
"value": "1000",
"realized": "true"
}
}
}
}
In modern versions of MySQL (>= 8.0.4) the query would be relatively simple (see JSON_TABLE):
SELECT
`milestone`.`id`,
SUM(`der`.`value`) `total`
FROM
`milestone`,
JSON_TABLE(
JSON_ARRAY(`json_value`),
'$[*]' COLUMNS(
NESTED PATH '$**.*' COLUMNS(
`value` DECIMAL(10, 2) PATH '$.value',
`realized` VARCHAR(5) PATH '$.realized'
)
)
) `der`
WHERE
`der`.`value` IS NOT NULL AND
`der`.`realized` = 'true'
GROUP BY
`milestone`.`id`;
See db-fiddle.
Here's a query that works on MySQL 5.7:
SELECT SUM(
JSON_UNQUOTE(
JSON_EXTRACT(
json_value,
CONCAT(
SUBSTRING_INDEX(
JSON_UNQUOTE(JSON_SEARCH(json_value, 'all', 'true')), '.', 4),
'.value'
)
)
)
) AS sum
FROM milestone
WHERE SUBSTRING_INDEX(
JSON_UNQUOTE(JSON_SEARCH(json_value, 'all', 'true')),
'.', -1) = 'realized'
This is quite complex, and hard to develop and maintain. Also it might not handle cases where you have multiple realized: true entries in a given JSON document. Or if there are other JSON keys besides "realized" that have the value "true". It will be quite tricky to even think of the edge cases, let alone handle them in code.
Out of curiosity, why not store this data as a traditional table?
CREATE TABLE milestone (
milestone_id INT NOT NULL,
date DATE NOT NULL,
value INT NOT NULL,
realized BOOL,
PRIMARY KEY (milestone_id, date)
);
INSERT INTO milestone VALUES
(1, '2018-05-04', 5000, NULL),
(1, '2018-12-04', 80000, false),
(2, '2016-12-04', 1000, true);
Then it's simpler to do your query:
SELECT SUM(value) FROM milestone WHERE realized = true;
I'm curious because I'm seeing more people using JSON in MySQL for cases where it would be easier to use normal tables and column. Easier to write the query, easier to understand the query, and more efficient to store the data and optimize the query performance.
Using JSON in the way you are doing makes queries more difficult than they should be.