[英]Generate nested nth level JSON SQL Server using recursive CTE
我有以下结构的数据
我想使用父属性 id 关系生成嵌套的 JSON 。
所需的 output。
[{
"propertyID": 1
, "title": "foo"
, "class": ""
, "typeid": 150
, "value": "bar"
, "children": [{}]
}, {
"propertyID": 2
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{}]
}, {
"propertyID": 3
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 4
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 41
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 411
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{}]
}]
},{
"propertyID": 42
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 421
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{}]
}]
}]
}, {
"propertyID": 5
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{}]
}, {
"propertyID": 6
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 7
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 8
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 9
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{
"propertyID": 10
, "title": "foo"
, "class": ""
, "typeid": 128
, "value": "bar"
, "children": [{}]
}]
}]
}]
}]
}]
}]
我在这里发现了一个类似的问题。 但我想在没有 function 的情况下做到这一点。 虽然这里很少有类似的问题,但没有什么符合我的要求。 到目前为止,我尝试了这个。 但是当同一个节点有两个孩子时它不起作用。 它复制了结果。
这是我尝试过的。
;WITH childrens AS (
SELECT propertyID, parentID, title, children
FROM CTE WHERE children = '[{}]'
union ALL
SELECT cte.propertyID, cte.parentID, cte.title, JSON_QUERY(
(SELECT c.propertyID, c.parentID, c.title, JSON_QUERY(c.children) AS children FOR JSON
PATH)
) AS children
FROM CTE
INNER JOIN childrens c on CTE.propertyID = c.parentID
), Tree as (
SELECT c.propertyID, c.parentID, c.title, JSON_QUERY(
(
SELECT ch.propertyID, ch.parentID, ch.title, JSON_QUERY(ch.children) AS children
FROM childrens ch
WHERE c.propertyID = ch.parentID FOR JSON PATH
)
) AS children
FROM childrens c WHERE parentID = 0
)
select DISTINCT *
from tree FOR JSON PATH
我得到的是
[
{
"propertyID":5,
"parentID":3,
"title":"foo",
"children":[
{
}
]
},
{
"propertyID":4,
"parentID":3,
"title":"foo",
"children":[
{
"propertyID":41,
"parentID":4,
"title":"foo",
"children":[
{
"propertyID":411,
"parentID":41,
"title":"foo",
"children":[
{
}
]
}
]
}
]
},
{
"propertyID":4,
"parentID":3,
"title":"foo",
"children":[
{
"propertyID":42,
"parentID":4,
"title":"foo",
"children":[
{
"propertyID":421,
"parentID":42,
"title":"foo",
"children":[
{
}
]
}
]
}
]
},
{
"propertyID":6,
"parentID":3,
"title":"foo",
"children":[
{
"propertyID":7,
"parentID":6,
"title":"foo",
"children":[
{
"propertyID":8,
"parentID":7,
"title":"foo",
"children":[
{
"propertyID":9,
"parentID":8,
"title":"foo",
"children":[
{
"propertyID":10,
"parentID":9,
"title":"foo",
"children":[
{
}
]
}
]
}
]
}
]
}
]
}
]
它复制属性= 4的节点。任何建议这样做。
sql 小提琴样品在这里
以多种不同的方式对此进行了多次讨论,在我看来,问题在于 SQL Server 无法在递归 CTE 中使用聚合,因此您无法递归聚合每一行的所有子项。
我发现的最简单的方法是(恐怖的恐怖!)一个标量用户定义函数(内联 TVF 不能递归)。
CREATE FUNCTION dbo.GetJson (@parentID int)
RETURNS nvarchar(max)
AS BEGIN
RETURN (
SELECT
propertyID,
title,
typeid,
[value],
children = JSON_QUERY(dbo.GetJson(propertyID))
FROM property p
WHERE parentID = @parentID
FOR JSON PATH
);
END;
SELECT
propertyID,
title,
typeid,
[value],
children = JSON_QUERY(dbo.GetJson(propertyID))
FROM property p
WHERE parentID IS NULL
FOR JSON PATH;
结果
[
{
"propertyID": 1,
"title": "foo",
"typeid": 150,
"value": "bar"
},
{
"propertyID": 2,
"title": "foo",
"typeid": 150,
"value": "bar"
},
{
"propertyID": 3,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 4,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 41,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 411,
"title": "foo",
"typeid": 150,
"value": "bar"
}
]
},
{
"propertyID": 42,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 421,
"title": "foo",
"typeid": 150,
"value": "bar"
}
]
}
]
},
{
"propertyID": 5,
"title": "foo",
"typeid": 150,
"value": "bar"
},
{
"propertyID": 6,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 7,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 8,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 9,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{
"propertyID": 10,
"title": "foo",
"typeid": 150,
"value": "bar"
}
]
}
]
}
]
}
]
}
]
}
]
这是一个递归解决方案。 它按路径顺序构造每行 json 对象,添加“子”元素,并在必要时关闭 arrays 和对象。 最终结果是单个对象的串联,包装为一个数组:
WITH
Recursion AS
(
-- Anchor part
SELECT
P.propertyID,
depth = 1,
rpath = CONVERT(nvarchar(4000), P.propertyID),
has_children =
IIF
(
EXISTS
(
SELECT C.*
FROM dbo.property AS C
WHERE C.parentId = P.propertyID
),
1, 0
),
element =
(
SELECT P.propertyID, P.title, P.typeid, P.[value]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
FROM dbo.property AS P
WHERE P.parentID = 0
UNION ALL
-- Recursive part
SELECT
P.propertyID,
depth = R.depth + 1,
rpath = CONCAT_WS(N'.', R.rpath, P.propertyID),
has_children =
IIF
(
EXISTS
(
SELECT C.*
FROM dbo.property AS C
WHERE C.parentId = P.propertyID
),
1, 0
),
element =
(
SELECT P.propertyID, P.title, P.typeid, P.[value]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
FROM Recursion AS R
JOIN dbo.property AS P
WITH (FORCESEEK)
ON P.parentID = R.propertyID
),
AddNextDepth AS
(
SELECT
R.*,
NextDepth =
LEAD(R.depth) OVER (
ORDER BY R.rpath)
FROM Recursion AS R
),
ModifiedTree AS
(
SELECT
ND.rpath,
element =
CONCAT
(
-- Insert "children" element
IIF
(
-- No children, add empty array
ND.has_children = 0,
STUFF
(
ND.element,
LEN(ND.element),
0,
N',"children":[{}]'
),
-- Insert "children" element
STUFF
(
ND.element,
LEN(ND.element),
1,
N',"children":['
)
),
-- Close previously opened array(s) if necessary
REPLICATE
(
N']}',
-- Number of closures needed
ND.depth - ISNULL(ND.NextDepth, 1)
),
-- Add comma if no children and not the last line
IIF
(
ND.has_children = 0
AND ND.NextDepth IS NOT NULL,
N',',
N''
)
)
FROM AddNextDepth AS ND
)
-- Concatenate objects in path order and add array wrapper
SELECT
CONCAT
(
N'[',
STRING_AGG(MT.element, N'')
WITHIN GROUP (ORDER BY MT.rpath),
N']'
)
FROM ModifiedTree AS MT;
Output:
[
{
"propertyID": 1,
"title": "foo",
"typeid": 150,
"value": "bar",
"children": [
{}
]
},
{
"propertyID": 2,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{}
]
},
{
"propertyID": 3,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 4,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 41,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 411,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{}
]
}
]
},
{
"propertyID": 42,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 421,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{}
]
}
]
}
]
},
{
"propertyID": 5,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{}
]
},
{
"propertyID": 6,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 7,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 8,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 9,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{
"propertyID": 10,
"title": "foo",
"typeid": 128,
"value": "bar",
"children": [
{}
]
}
]
}
]
}
]
}
]
}
]
}
]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.