22

Using only the native JSON fuctions (no PHP, etc) in MySQL version 5.7.12 (section 13.16 in the manual) I am trying to write a query to generate a JSON document from relational tables that contains a sub object. Given the following example:

CREATE TABLE `parent_table` (
   `id` int(11) NOT NULL,
   `desc` varchar(20) NOT NULL,
   PRIMARY KEY (`id`)
);
CREATE TABLE `child_table` (
   `id` int(11) NOT NULL,
   `parent_id` int(11) NOT NULL,
   `desc` varchar(20) NOT NULL,
   PRIMARY KEY (`id`,`parent_id`)
);
insert `parent_table` values (1,'parent row 1');
insert `child_table` values (1,1,'child row 1');
insert `child_table` values (2,1,'child row 2');

I am trying to generate a JSON document that looks like this:

[{
    "id" : 1,
    "desc" : "parent row 1",
    "child_objects" : [{
            "id" : 1,
            "parent_id" : 1,
            "desc" : "child row 1"
        }, {
            "id" : 2,
            "parent_id" : 1,
            "desc" : "child row 2"
        }
    ]
}]

I am new to MySQL and suspect there is a SQL pattern for generating nested JSON objects from one to many relationships but I'm having trouble finding it.

In Microsoft SQL (which I'm more familiar with) the following works:

select 
 [p].[id]
,[p].[desc]
,(select * from [dbo].[child_table] where [parent_id] = [p].[id] for json auto) AS [child_objects]
from [dbo].[parent_table] [p]
for json path

I attempted to write the equivalent in MySQL as follows:

select json_object(
 'id',p.id 
,'desc',p.`desc`
,'child_objects',(select json_object('id',id,'parent_id',parent_id,'desc',`desc`) 
                  from child_table where parent_id = p.id)
)
from parent_table p;

select json_object(
  'id',p.id 
 ,'desc',p.`desc`
 ,'child_objects',json_array((select json_object('id',id,'parent_id',parent_id,'desc',`desc`) 
                              from child_table where parent_id = p.id))
 )
 from parent_table p

Both attempts fail with the following error:

Error Code: 1242. Subquery returns more than 1 row

4 Answers 4

37

The reason you are getting these errors is that the parent json object is not expecting a result set as one of its inputs, you need to have simple object pairs like {name, string} etc bug report - may be available in future functionality... this just means that you need to convert your multi row results into a concatination of results separated by commas and then converted into a json array.

You almost had it with your second example.

You can achieve what you are after with the GROUP_CONCAT function

select json_object(
  'id',p.id 
 ,'desc',p.`desc`
 ,'child_objects',json_array(
                     (select GROUP_CONCAT(
                                 json_object('id',id,'parent_id',parent_id,'desc',`desc`)
                             )   
                      from child_table 
                      where parent_id = p.id))
                   )
 from parent_table p;

This almost works, it ends up treating the subquery as a string which leaves the escape characters in there.

'{\"id\": 1, 
\"desc\": \"parent row 1\", 
\"child_objects\": 
    [\"
    {\\\"id\\\": 1,
     \\\"desc\\\": \\\"child row 1\\\", 
    \\\"parent_id\\\": 1
    },
    {\\\"id\\\": 2, 
    \\\"desc\\\": \\\"child row 2\\\", 
    \\\"parent_id\\\": 1}\"
    ]
}'

In order to get this working in an appropriate format, you need to change the way you create the JSON output as follows:

select json_object(
  'id',p.id 
 ,'desc',p.`desc`
 ,'child_objects',(select CAST(CONCAT('[',
                GROUP_CONCAT(
                  JSON_OBJECT(
                    'id',id,'parent_id',parent_id,'desc',`desc`)),
                ']')
         AS JSON) from child_table where parent_id = p.id)

 ) from parent_table p;

This will give you the exact result you require:

'{\"id\": 1, 
\"desc\": \"parent row 1\", 
\"child_objects\": 
    [{\"id\": 1, 
    \"desc\": \"child row 1\", 
    \"parent_id\": 1
    }, 
    {\"id\": 2, 
    \"desc\": \"child row 2\", 
    \"parent_id\": 1
    }]  
}'
Sign up to request clarification or add additional context in comments.

6 Comments

hello, i am not able to use json_object this function. #1305 - FUNCTION json_object does not exist. What to do???
Check which version of Mysql you are using. The older versions do not support json_object
current version is 5.6
JSON aggregate functions JSON_ARRAYAGG() and JSON_OBJECTAGG() are now available in MySQL 8 mysqlserverteam.com/mysql-8-0-labs-json-aggregation-functions
This works fine but for two level only, can we get till 'n' level
|
7

For MariaDb, CAST AS JSON does not work. But JSON_EXTRACT may be used to convert a string to a JSON object:

select json_object(
  'id',p.id 
 ,'desc',p.`desc`
 ,'child_objects',JSON_EXTRACT(IFNULL((select
    CONCAT('[',GROUP_CONCAT(
      json_object('id',id,'parent_id',parent_id,'desc',`desc`)
    ),']')   
   from child_table where parent_id = p.id),'[]'),'$')
 ) from parent_table p;

Comments

0

I tried group_concat solution but I found it's problems larger string because of group_concat limitations (group_concat_max_len). I wrote the new function resolve the problem about converting a string to JSON object as bellow and how to use it. Tested on MariaDB 10.5.12

Usage: https://i.sstatic.net/cWfd7.jpg

    CREATE FUNCTION `ut_tf_array`(input_json longtext) RETURNS longtext CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci
    COMMENT 'Function for transform json array agg'
BEGIN

    DECLARE transformed_data_list longtext ;
    DECLARE record longtext ;
    DECLARE i_count int ;    
    DECLARE i_count_items int ;  
    

    SET i_count = 0;
    SET i_count_items = JSON_LENGTH(JSON_EXTRACT(input_json,'$'));
    
    SET transformed_data_list = '[]';
    
    -- return array with length = zero
    IF input_json is NULL THEN
        RETURN transformed_data_list;
    END IF;

    WHILE i_count < i_count_items DO
      -- fetch into record  
      SELECT JSON_EXTRACT( JSON_EXTRACT( input_json ,'$') , CONCAT('$[',i_count,']')) INTO record;
      -- append to transformed_data_list    
      SELECT JSON_ARRAY_APPEND(transformed_data_list, '$', JSON_EXTRACT(record, '$')) into transformed_data_list;

      SET i_count := i_count + 1;
    END WHILE;
    
    -- done
    RETURN transformed_data_list;


END

Comments

0

Below Query works for me.

SELECT JSON_ARRAYAGG(JSON_OBJECT('Id', p.id, 'desc', p.`desc`, 'child_objects', temp_json)) AS json_value
FROM (
SELECT p.id, p.`desc`,
JSON_ARRAYAGG(JSON_OBJECT('id', p.id, 'parent_id', p.parent_id, 'desc', p.`desc`)) AS temp_json
FROM parent_table p
GROUP BY p.id
) t;

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.