5

I have a table with JSON field which contains an array of JSON objects. I need to select objects by some condition.

Create and fill a table:

CREATE TABLE test (
    id INT AUTO_INCREMENT PRIMARY KEY,
    json_list JSON
);

INSERT INTO test(json_list) VALUES
("{""list"": [{""type"": ""color"", ""value"": ""red""}, {""type"": ""shape"", ""value"": ""oval""}, {""type"": ""color"", ""value"": ""green""}]}"),
("{""list"": [{""type"": ""shape"", ""value"": ""rect""}, {""type"": ""color"", ""value"": ""olive""}]}"),
("{""list"": [{""type"": ""color"", ""value"": ""red""}]}")
;

Now I need to select all objects with type = color from all rows.

I want to see this output:

id  extracted_value
1   {"type": "color", "value": "red"}
1   {"type": "color", "value": "green"}
2   {"type": "color", "value": "olive"}
3   {"type": "color", "value": "red"}

It would be good to get this too:

id  color
1   red
1   green
2   olive
3   red

I can't change the DB or JSON.

I'm using MySQL 5.7

My current solution

My solution is to cross join the table with some index set and then extract all elements of JSON array.

I don't like it as if possible object count in one array is large it is required to have all indexes till the maximum one. It makes the query slow as it won't stop calculation of JSON value when the end of array is reached.

SELECT 
    test.id,
    JSON_EXTRACT(test.json_list, CONCAT('$.list[', ind.ind, ']')),
    ind.ind
FROM
    test
CROSS JOIN
    (SELECT 0 AS ind UNION ALL SELECT 1 AS ind UNION ALL SELECT 2 AS ind) ind
WHERE
    JSON_LENGTH(json_list, "$.list") > ind.ind
    AND JSON_EXTRACT(json_list, CONCAT('$.list[', ind.ind, '].type')) = "color";

It is easy to get only values by changing JSON_EXTRACT path. But is it there a better way?

Edits

  • Added a check for json_list.list length. This filtered out 67% of derived table rows in this case.
2
  • 1
    Not much you can do with 5.7. I would limit the JOIN using the length of the array: json_length(json_list->'$.list') > ind.ind Commented Jun 4, 2020 at 7:58
  • @PaulSpiegel checking length should help. Thank you. Will edit my solution. Commented Jun 4, 2020 at 8:05

4 Answers 4

6

So current best solution is mine:

SELECT 
    test.id,
    JSON_EXTRACT(test.json_list, CONCAT('$.list[', ind.ind, ']')),
    ind.ind
FROM
    test
CROSS JOIN
    (SELECT 0 AS ind UNION ALL SELECT 1 AS ind UNION ALL SELECT 2 AS ind) ind
WHERE
    JSON_LENGTH(json_list, "$.list") > ind.ind
    AND JSON_EXTRACT(json_list, CONCAT('$.list[', ind.ind, '].type')) = "color";
Sign up to request clarification or add additional context in comments.

Comments

5
SELECT JSON_EXTRACT(json_list, '$.list[*]')
FROM `test`
where JSON_CONTAINS(json_list, '{"type":"color"}', '$.list')

3 Comments

Thiw will give json arrays for rows which fit condition: For the example it would give 3 rows instead of 4. Each result will contain all json array elements.This is not what is needed
can you provide why this solution answer the question?
This is helpful, why no one is upvotting this answer?
0

It's working for me here multiple filter use

SELECT id, JSON_EXTRACT(floorplan, '$') FROM table_name WHERE JSON_CONTAINS(floorplan, '{"Bed":0}', '$') OR JSON_CONTAINS(floorplan, '{"Bed":3}', '$');

'[{"Bed": 2, "MinimumRent": 1895}, {"Bed": 3, "MinimumRent": 2325}]'),

Comments

0

In mysql 8 (and no one should be running any unsupported and insecure earlier versions), you use JSON_TABLE for this:

select test.id, json_list.value color
from test
join json_table(
  json_extract(test.json_list, "$.list"), "$[*]"
  columns (type text path "$.type", value text path "$.value")
) json_list on json_list.type='color

fiddle

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.