我在SQL Server 2012中有一个表,其中包含以下自加数据:
ID | ChildA_ID | ChildB_ID
(int) | (int) | (int)
-----------------------------
1 | |
2 | 1 |
3 | 1 |
4 | 3 |
6 | 2 | 4
7 | 6 |
使用T-SQL(或者,压缩,CLR代码),我想把它变成看起来像这样的XML:
<row>
<ID>7</ID>
<ChildA>
<row>
<ID>6</ID>
<ChildA>
<row>
<ID>2</ID>
<ChildA>
<row>
<ID>1</ID>
</row>
</ChildA>
</row>
</ChildA>
<ChildB>
<row>
<ID>4</ID>
<ChildA>
<row>
<ID>3</ID>
<ChildA>
<row>
<ID>1</ID>
</row>
</ChildA>
</row>
</ChildA>
</row>
</ChildB>
</row>
</ChildA>
</row>
我对SQL Server中的递归CTE和FOR XML
子句有相当多的经验,但这个让我很好并且真正难倒。 (我不打算说我只是拉扯哑巴的可能性。)
欢迎提出任何建议,包括构建数据的其他方法,以达到预期的效果。 (但请注意,“ChildA”和“ChildB”代表父子关系的不同类型,而不是非规范化。)
当使用我的样本数据尝试Mitch的解决方案时,我遇到了一些重复的行:
<row>
<ID>7</ID>
<ChildA>
<row>
<ID>6</ID>
<ChildA>
<row>
<ID>2</ID>
<ChildA>
<row>
<ID>1</ID>
</row>
</ChildA>
</row>
<!--This is the first duplicate row:-->
<row>
<ID>2</ID>
<ChildA>
<row>
<ID>1</ID>
</row>
</ChildA>
</row>
</ChildA>
<ChildB>
<row>
<ID>4</ID>
<ChildA>
<!--This is the second "duplicate" row:-->
<row>
<ID>3</ID>
</row>
<row>
<ID>3</ID>
<ChildA>
<row>
<ID>1</ID>
</row>
</ChildA>
</row>
</ChildA>
</row>
</ChildB>
</row>
</ChildA>
</row>
以下是我用于生成上述结果的代码(Mitch解决方案的略微修改版本):
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Nodes')
CREATE TABLE Nodes (id int, leftid int, rightid int);
DELETE FROM Nodes
INSERT INTO Nodes
VALUES (1, null, null),
(2, 1, null),
(3, 1, null),
(4, 3, null),
(6, 2, 4),
(7, 6, null);
CREATE TABLE #xml (id int, depth int, data xml);
DECLARE @rootNode int = 7;
WITH cte (cid, depth, child) as
(
-- anchor
SELECT id, 1, leftid
FROM nodes
WHERE id = @rootNode and leftid is not null
UNION ALL
SELECT id, 1, rightid
FROM nodes
WHERE id = @rootNode and rightid is not null
UNION ALL
-- recursive
SELECT n.id, depth + 1, leftid
FROM cte c
INNER JOIN nodes n on c.child = n.id
WHERE leftid is not null or (rightid is null and leftid is null)
UNION ALL
SELECT n.id, depth + 1, rightid
FROM cte c
INNER JOIN nodes n on c.child = n.id
WHERE rightid is not null
)
INSERT INTO #xml (id, depth, data)
SELECT DISTINCT cid, depth, null
FROM CTE;
DECLARE @maxDepth int;
SET @maxDepth = (SELECT MAX(depth) FROM #xml)
WHILE @MaxDepth > 0
BEGIN
UPDATE xu
SET data = (
SELECT n.ID, xl.data as ChildA, xr.data as ChildB
FROM Nodes n
LEFT OUTER JOIN #xml xl on n.leftid = xl.id
LEFT OUTER JOIN #xml xr on n.rightid = xr.id
WHERE xu.id = n.id
FOR XML PATH('row'), TYPE
)
FROM #xml xu
WHERE xu.Depth = @MaxDepth
SET @MaxDepth = @MaxDepth - 1
END
SELECT data FROM #xml WHERE id = @RootNode
DROP TABLE #xml
答案 0 :(得分:2)
您可以通过深度优先搜索来执行这些类型的翻译。使用递归CTE计算深度,然后使用循环转换为XML:
CREATE TABLE Nodes (id int, leftid int, rightid int, name varchar(256));
INSERT INTO Nodes
VALUES (1, 2, 3, 'abcd'),
(2, 4, 5, 'ab'),
(4, null, null, 'a'),
(5, null, null, 'b'),
(3, 6, 7, 'cd'),
(6, null, null, 'c'),
(7, null, null, 'd');
CREATE TABLE #xml (id int, depth int, data xml, parent int);
DECLARE @rootNode int = 1;
WITH cte (cid, depth, child, parent) as
(
-- anchor
SELECT id, 1, leftid, cast(null as int)
FROM nodes
WHERE id = @rootNode and leftid is not null
UNION ALL
SELECT id, 1, righted, null
FROM nodes
WHERE id = @rootNode and rightid is not null
UNION ALL
-- recursive
SELECT n.id, depth + 1, leftid, c.cid
FROM cte c
INNER JOIN nodes n on c.child = n.id
WHERE leftid is not null or (rightid is null and leftid is null)
UNION ALL
SELECT n.id, depth + 1, rightid, c.cid
FROM cte c
INNER JOIN nodes n on c.child = n.id
WHERE rightid is not null
)
INSERT INTO #xml (id, depth, data, parent)
SELECT DISTINCT cid, depth, null, parent
FROM CTE;
DECLARE @maxDepth int;
SET @maxDepth = (SELECT MAX(depth) FROM #xml)
WHILE @MaxDepth > 0
BEGIN
UPDATE xu
SET data = (
SELECT n.Name, xl.data as [Left], xr.data as [Right]
FROM Nodes n
LEFT OUTER JOIN #xml xl on n.leftid = xl.id and xl.parent = n.id
LEFT OUTER JOIN #xml xr on n.rightid = xr.id and xr.parent = n.id
WHERE xu.id = n.id
FOR XML PATH('TreeNode'), TYPE
)
FROM #xml xu
WHERE xu.Depth = @MaxDepth
SET @MaxDepth = @MaxDepth - 1
END
SELECT data FROM #xml WHERE id = @RootNode
制作:
<TreeNode>
<Name>abcd</Name>
<Left>
<TreeNode>
<Name>ab</Name>
<Left>
<TreeNode>
<Name>a</Name>
</TreeNode>
</Left>
<Right>
<TreeNode>
<Name>b</Name>
</TreeNode>
</Right>
</TreeNode>
</Left>
<Right>
<TreeNode>
<Name>cd</Name>
<Left>
<TreeNode>
<Name>c</Name>
</TreeNode>
</Left>
<Right>
<TreeNode>
<Name>d</Name>
</TreeNode>
</Right>
</TreeNode>
</Right>
</TreeNode>
答案 1 :(得分:1)
sql可以做很多丑陋的事情;诀窍是将递归移到手工制作的堆栈,因为反复调用的奢侈品在某种程度上是有限的:
declare @tmp table(id int, ChildA_id int, ChildB_ID int)
declare @xml xml
select @xml = ''
insert into @tmp
select 1 , NULL , NULL
union select 2 , 1 , NULL
union select 3 , 1 , NULL
union select 4 , 3 , NULL
union select 6 , 2 , 4
union select 7 , 6 , NULL
declare @stack table(id int, parent int, relation nvarchar,
stackindex int identity(1,1))
insert into @stack
select max(id), null, 'R' from @tmp
declare @stackindex int, @id int, @relation nvarchar(50),
@parent int, @ChildA_id int, @ChildB_id int
while (select count(1) from @stack) > 0
begin
select @stackindex = max(stackindex) from @stack
select @id = id, @relation = relation, @parent = parent
from @stack where stackindex = @stackindex
if @id is not null
begin
select @childa_id = ChildA_id, @childb_id = ChildB_id
from @tmp where id = @id
if @childa_id is not null
insert into @stack(id,parent,relation)
values(@childa_id, @id, 'A')
if @childb_id is not null
insert into @stack(id,parent,relation)
values(@childb_id, @id, 'B')
if @relation = 'R'
SET @xml.modify('
insert <row><ID>{sql:variable("@id")}</ID></row>
as last
into (.)')
if @relation = 'A'
SET @xml.modify('
insert <ChildA><row><ID>{sql:variable("@id")}</ID></row></ChildA>
after (//row/ID[text()=sql:variable("@parent")])[1]')
if @relation = 'B'
SET @xml.modify('
insert <ChildB><row><ID>{sql:variable("@id")}</ID></row></ChildB>
as last
into (//row[ID/text()=sql:variable("@parent")])[1]')
end
delete from @stack where stackindex = @stackindex
end
select @xml