Иерархический вывод JSON из таблицы - PullRequest
1 голос
/ 25 сентября 2019

У меня есть эта структура таблицы

| User | Type    | Data |
|------|---------|------|
| 1    | "T1"    | "A"  |
| 1    | "T1"    | "B"  |
| 1    | "T2"    | "C"  |
| 2    | "T1"    | "D"  |

Я хочу получить иерархическую строку JSON, возвращаемую из моего запроса

{
  "1": {
    "T1": [
      "A",
      "B"
    ],
    "T2": [
      "C"
    ]
  },
  "2": {
    "T1": [
      "D"
    ]
  }
}

Итак, одна запись для каждого User свложенная запись для каждого Type, а затем вложенная запись для каждого Data

Все, что я нахожу, это оператор FOR JSON PATH, ROOT ('x') или AUTO, но ничего, что могло бы сделать эту иерархическую.Это вообще возможно из коробки?Я ничего не мог найти, поэтому я экспериментировал с (рекурсивным) CTE, но не очень далеко.Я был бы очень признателен, если бы кто-то мог просто указать мне правильное направление.

Ответы [ 2 ]

1 голос
/ 25 сентября 2019

Я не уверен, что вы можете создать JSON с именами переменных, используя FOR JSON AUTO и FOR JSON PATH.Я предлагаю следующие решения:

  • с использованием FOR XML PATH для генерации JSON со строковыми манипуляциями
  • с использованием STRING_AGG() для генерации JSON со строковыми манипуляциями для SQL Server 2017+
  • с использованием STRING_AGG() и JSON_MODIFY() для SQL Server 2017 +

Таблица:

CREATE TABLE #InputData (
   [User] int,
   [Type] varchar(2),
   [Data] varchar(1)
)
INSERT INTO #InputData 
   ([User], [Type], [Data])
VALUES
   (1, 'T1', 'A'),
   (1, 'T1', 'B'),
   (1, 'T2', 'C'),
   (2, 'T1', 'D')

Оператор с использованием FOR XML PATH:

;WITH SecondLevelCTE AS (
   SELECT 
      d.[User],
      d.[Type],
      Json1 = CONCAT(
         '[', 
         STUFF(
         (
         SELECT CONCAT(',"', [Data], '"')
         FROM #InputData 
         WHERE [User] = d.[User] AND [Type] = d.[Type]
         FOR XML PATH('')
         ), 1, 1, ''),
         ']')
   FROM #InputData d
   GROUP BY d.[User], d.[Type]
), FirstLevelCTE AS (
   SELECT 
      d.[User],
      Json2 = CONCAT(
         '{',
         STUFF(
         (
         SELECT CONCAT(',"', [Type], '":', [Json1])
         FROM SecondLevelCTE 
         WHERE [User] = d.[User]
         FOR XML PATH('')
         ), 1, 1, ''),
         '}'
      )
   FROM SecondLevelCTE d
   GROUP BY d.[User]
)
SELECT CONCAT(
   '{',
   STUFF(
   (
   SELECT CONCAT(',"', [User], '":', Json2)
   FROM FirstLevelCTE
   FOR XML PATH('')
   ), 1, 1, '')   ,
   '}'
)

Использование оператора STRING_AGG():

;WITH SecondLevelCTE AS (
   SELECT 
      d.[User],
      d.[Type],
      Json1 = (
         SELECT CONCAT('["', STRING_AGG([Data], '","'), '"]')
         FROM #InputData 
         WHERE [User] = d.[User] AND [Type] = d.[Type]
      )
   FROM #InputData d
   GROUP BY d.[User], d.[Type]
), FirstLevelCTE AS (
   SELECT 
      d.[User],
      Json2 = (
         SELECT STRING_AGG(CONCAT('"', [Type], '":', [Json1]), ',')
         FROM SecondLevelCTE
         WHERE [User] = d.[User]
      )
   FROM SecondLevelCTE d
   GROUP BY d.[User]
)
SELECT CONCAT('{', STRING_AGG(CONCAT('"', [User], '":{', Json2, '}'), ','), '}')
FROM FirstLevelCTE

Использование оператора STRING_AGG() и JSON_MODIFY():

DECLARE @json nvarchar(max) = N'{}'
SELECT 
   @json = JSON_MODIFY(
      CASE 
         WHEN JSON_QUERY(@json, CONCAT('$."', [User] , '"')) IS NULL THEN JSON_MODIFY(@json, CONCAT('$."', [User] , '"'), JSON_QUERY('{}'))
         ELSE @json
      END,
      CONCAT('$."', [User] , '".', [Type]), 
      JSON_QUERY(Json)
   )
FROM (
   SELECT 
      d.[User],
      d.[Type],
      Json = (
         SELECT CONCAT('["', STRING_AGG([Data], '","'), '"]')
         FROM #InputData 
         WHERE [User] = d.[User] AND [Type] = d.[Type]
      )
   FROM #InputData d
   GROUP BY d.[User], d.[Type]
) t

Вывод:

{"1":{"T1":["A","B"],"T2":["C"]},"2":{"T1":["D"]}}
1 голос
/ 25 сентября 2019

Это не совсем то, что вы хотите (я не очень хорош для FOR JSON), но оно действительно приближает вас к нужной форме, пока не придет что-то лучшее ... (https://jsonformatter.org/json-parser/974b6b)

use tempdb
GO

drop table if exists users
create table users (
    [user] integer
    , [type] char(2)
    , [data] char(1)
)


insert into users 
values (1, 'T1', 'A')
    , (1, 'T1', 'B')
    , (1, 'T2', 'C')
    , (2, 'T1', 'D')

select DISTINCT ONE.[user], two.[type], three.[data] 
from users AS ONE
inner join users two
    on one.[user] = two.[user]
inner join users three
    on one.[user] = three.[user]
    and two.[type] = three.[type]
for JSON AUTO
...