【问题标题】:Hierarchical JSON output from table表中的分层 JSON 输出
【发布时间】:2019-09-25 04:41:54
【问题描述】:

我有这个表结构

| 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,但没有走得太远。如果有人能指出我正确的方向,我将不胜感激。

【问题讨论】:

    标签: json sql-server sql-server-2016 common-table-expression hierarchical-data


    【解决方案1】:

    我不确定您是否可以使用FOR JSON AUTOFOR JSON PATH 创建带有变量键名的JSON。我建议以下解决方案:

    • 使用FOR XML PATH 通过字符串操作生成JSON
    • 使用STRING_AGG() 生成JSON 以及SQL Server 2017+ 的字符串操作
    • 在 SQL Server 2017+ 中使用 STRING_AGG()JSON_MODIFY()

    表:

    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"]}}
    

    【讨论】:

    • 好东西。我仅限于 SQL2016,所以最适合我的。我已经做了更多的阅读,似乎没有其他办法。不过,通过字符串操作等,这似乎不是一种表现良好的方式......
    【解决方案2】:

    这并不是你想要的(我不喜欢 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
    

    【讨论】:

    • 谢谢。是的,似乎没有办法绕过字符串操作来实现这一点。可惜我需要它才能表现得非常好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-01
    • 1970-01-01
    • 2021-11-30
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2020-07-20
    相关资源
    最近更新 更多