Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate hierarchical JSON data with Microsoft SQL Server 2016?

I'm using Microsoft SQL Server 2016. This version supports JSON.

I have a Person table with the following data:

PersonId FatherId Name
1 NULL 4th Grand Father
2 1 3rd Grand Father
3 2 2nd Grand Father
4 3 Grand Father
5 4 Father
6 4 Uncle
7 6 Cousin
8 5 Brother
9 5 Me

I run the following query:

WITH Persons_CTE AS(
    SELECT PersonId, FatherId, Name FROM Persons WHERE FatherId IS NULL
    UNION ALL
    SELECT P.PersonId, P.FatherId, P.Name FROM Persons P JOIN Persons_CTE PCTE
    ON PCTE.PersonId = P.FatherId)

    SELECT P.Name as Name, PCTE.Name as Children FROM Persons_CTE PCTE LEFT JOIN Persons P
    ON P.PersonId = PCTE.FatherId 
    FOR JSON PATH

The query generates the following result:

[
   {
      "Children":"4th Grand Father"
   },
   {
      "Name":"4th Grand Father",
      "Children":"3rd Grand Father"
   },
   {
      "Name":"3rd Grand Father",
      "Children":"2nd Grand Father"
   },
   {
      "Name":"2nd Grand Father",
      "Children":"Grand Father"
   },
   {
      "Name":"Grand Father",
      "Children":"Father"
   },
   {
      "Name":"Grand Father",
      "Children":"Uncle"
   },
   {
      "Name":"Uncle",
      "Children":"Cousin"
   },
   {
      "Name":"Father",
      "Children":"Brother"
   },
   {
      "Name":"Father",
      "Children":"Me"
   }
]

I want the query result to the following hierarchical format. How can I do it?

[
   {
      "Name":"4th Grand Father",
      "Children":[
         {
            "Name":"3rd Grand Father",
            "Children":[
               {
                  "Name":"2nd Grand Father",
                  "Children":[
                     {
                        "Name":"Grand Father",
                        "Children":[
                           {
                              "Name":"Father",
                              "children":[
                                 {
                                    "Name":"Brother"
                                 },
                                 {
                                    "Name":"Me"
                                 }
                              ]
                           },
                           {
                              "Name":"Uncle",
                              "children":[
                                 {
                                    "Name":"Cousin"
                                 }
                              ]
                           }
                        ]
                     }
                  ]
               }
            ]
         }
      ]
   }
]
like image 282
wonderful world Avatar asked Jul 11 '16 03:07

wonderful world


2 Answers

Sure It would be very hard (if not impossible) to implement a Json tree using recursive CTE.

However, using recursive function is really helpful and solves the Problem:

ALTER  FUNCTION fn_Json(@PersonId INT, @IsRoot INT ) 
RETURNS VARCHAR(MAX)
BEGIN 
    DECLARE @Json NVARCHAR(MAX) = '{}', @Name NVARCHAR(MAX) , @Children                 NVARCHAR(MAX)

    SET @Json =  
    (SELECT P.Name ,JSON_QUERY(dbo.fn_Json(P.PersonId, 2) ) AS Children 
    FROM dbo.Persons AS P  
    WHERE P.FatherId = @PersonId 
    FOR JSON AUTO);

    IF(@IsRoot = 1) 
    BEGIN
       SELECT @Name = P.Name FROM dbo.Persons AS P WHERE P.PersonId = @PersonId
       SET @Json =   '{"Name":"' + @Name + '","Children":' + CAST(@Json AS NVARCHAR(MAX)) + '}'
       SET @IsRoot = 2
    END

    RETURN @Json 
END 

GO

It worth mentioning that functions can not be built if its interior objects are invalid. Therefore, it is necessary to build the function as:

CREATE FUNCTION fn_Json(@PersonId INT, @IsRoot INT) 
RETURNS VARCHAR(MAX)
BEGIN 
   RETURN 1
END 

and then to use the fist code. If you want that the root node is included set

@IsRoot = 1

if not @IsRoot = 2 or some other values

like image 70
Afshin Amiri Avatar answered Oct 19 '22 06:10

Afshin Amiri


Unfortunately, recursive CTe cannot be used to generate hierarchical json . Output of recursive CTE is still flat result.

The only way to create hierarchical output is to create separate CTE for each level and then join the using FOR JSON AUTO

Prepare table:

declare @t table (PersonId int, FatherId int, Name nvarchar(20));

insert into @t(PersonId, FatherId, Name)
values
(1, NULL, '4th Grand Father'),
(2, 1, '3rd Grand Father'),
(3, 2, '2nd Grand Father'),
(4, 3, 'Grand Father'),
(5, 4, 'Father'),
(6, 4, 'Uncle'),
(7, 6, 'Cousin'),
(8, 5, 'Brother'),
(9, 5, 'Me');

-- Hierarchical query:

WITH
Persons_CTE1 AS(
    SELECT PersonId, FatherId, Name FROM @t WHERE FatherId IS NULL
),
Persons_CTE2 AS(
    SELECT P.PersonId, P.FatherId, P.Name
    from @t P
    WHERE P.FatherId IN (SELECT PersonId FROM Persons_CTE1)
),
Persons_CTE3 AS(
    SELECT P.PersonId, P.FatherId, P.Name
    from @t P
    WHERE P.FatherId IN (SELECT PersonId FROM Persons_CTE2)
),
Persons_CTE4 AS(
    SELECT P.PersonId, P.FatherId, P.Name
    from @t P
    WHERE P.FatherId IN (SELECT PersonId FROM Persons_CTE3)
),
Persons_CTE5 AS(
    SELECT P.PersonId, P.FatherId, P.Name
    from @t P
    WHERE P.FatherId IN (SELECT PersonId FROM Persons_CTE4)
),
Persons_CTE6 AS(
    SELECT P.PersonId, P.FatherId, P.Name
    from @t P
    WHERE P.FatherId IN (SELECT PersonId FROM Persons_CTE5)
)
select Persons_CTE1.Name, Persons_CTE2.Name, Persons_CTE3.Name,
       Persons_CTE4.Name, Persons_CTE5.Name, Persons_CTE6.Name
from Persons_CTE1
    LEFT JOIN Persons_CTE2
        ON Persons_CTE2.FatherId = Persons_CTE1.PersonId
    LEFT JOIN Persons_CTE3
        ON Persons_CTE3.FatherId = Persons_CTE2.PersonId
    LEFT JOIN Persons_CTE4
        ON Persons_CTE4.FatherId = Persons_CTE3.PersonId
    LEFT JOIN Persons_CTE5
        ON Persons_CTE5.FatherId = Persons_CTE4.PersonId
    LEFT JOIN Persons_CTE6
        ON Persons_CTE6.FatherId = Persons_CTE5.PersonId
FOR JSON AUTO
like image 6
Jovan MSFT Avatar answered Oct 19 '22 06:10

Jovan MSFT