来自 table 的分层 JSON 输出
Hierarchical JSON output from table
我有这个table结构
| 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,但没有走得太远。如果有人能指出正确的方向,我将不胜感激。
这不是您想要的(我对 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
我不确定您是否可以使用 FOR JSON AUTO
和 FOR JSON PATH
创建具有可变键名的 JSON
。我建议以下解决方案:
- 使用
FOR XML PATH
通过字符串操作生成 JSON
- 使用
STRING_AGG()
通过 SQL Server 2017+ 的字符串操作生成 JSON
- 为 SQL Server 2017+
使用 STRING_AGG()
和 JSON_MODIFY()
Table:
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"]}}
我有这个table结构
| 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,但没有走得太远。如果有人能指出正确的方向,我将不胜感激。
这不是您想要的(我对 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
我不确定您是否可以使用 FOR JSON AUTO
和 FOR JSON PATH
创建具有可变键名的 JSON
。我建议以下解决方案:
- 使用
FOR XML PATH
通过字符串操作生成JSON
- 使用
STRING_AGG()
通过 SQL Server 2017+ 的字符串操作生成 - 为 SQL Server 2017+ 使用
JSON
STRING_AGG()
和 JSON_MODIFY()
Table:
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"]}}