如何在SQL中展平树叶序列

时间:2013-06-20 16:51:04

标签: sql-server xml sql-server-2008 tsql xml-parsing

我有一个表S,其中包含2列:Name用于ID和XML列。
此表表示树数据。

S
-----+----------------
Name | XmlCol
-----+----------------
 A   | <E><B Id='b1' Type=0 /><B Id='D' Type=1 /><B Id='b2' Type=0 /></E>
 D   | <E><B Id='b3' Type=0 /><B Id='G' Type=1 /></E>
 F   | <E><B Id='b4' Type=0 /></E>
 G   | <E><B Id='b5' Type=0 /></E>

数据按给定顺序显示。 此处的订单很重要。

注意XML结构。

Type = 0表示该条目的类型为leaf Type = 1表示表中有一行具有相同的Name,因此是节点而不是叶子。

有5片叶子,b1,b2,b3,b4,b5。

我想要的是获得所有序列中所有叶子的表,如下所示:

Leaves
--------
b1
b3
b5
b2
b4

当起始节点为'A'

这是XML解析片段,但它只是开始。

SELECT [Id] = xTree.b.value('@Id', 'varchar(10)')
FROM [S]
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)

有人可以建议如何在SQL中执行此操作吗?

1 个答案:

答案 0 :(得分:1)

尝试这样的事情:

DECLARE @Source TABLE (
    Name VARCHAR(10) PRIMARY KEY,
    XmlCol XML NOT NULL
)

DECLARE @Root VARCHAR(10)='A'

INSERT INTO @Source VALUES 
('A',CONVERT(XML,'<E><B Id="b1" Type="0" /><B Id="D" Type="1" /><B Id="b2" Type="0" /></E>')),
('D',N'<E><B Id="b3" Type="0" /><B Id="G" Type="1" /></E>'),
('F',N'<E><B Id="b4" Type="0" /></E>'),
('G',N'<E><B Id="b5" Type="0" /></E>')

DECLARE @Temp1 TABLE (  
    ID INT IDENTITY PRIMARY KEY,
    Name VARCHAR(10) NOT NULL,
    Type INT NOT NULL,
    Value VARCHAR(10) NOT NULL
)
INSERT INTO @Temp1
SELECT Name, xTree.b.value('@Type', 'int') AS Type, xTree.b.value('@Id', 'varchar(10)') AS Value
FROM @Source
CROSS APPLY [XmlCol].nodes('/E/B') AS xTree(b)

DECLARE @Temp2 TABLE (  
    ID INT PRIMARY KEY,
    Name VARCHAR(10) NOT NULL,
    Type INT NOT NULL,
    Value VARCHAR(10) NOT NULL,
    Position INT NOT NULL
)

INSERT INTO @Temp2
SELECT *, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY ID) AS Position
FROM @Temp1

DECLARE @Temp3 TABLE (
    Name VARCHAR(10) NOT NULL,
    Type INT NOT NULL,
    Value VARCHAR(10) NOT NULL,
    Position FLOAT NOT NULL,
    Level INT NOT NULL,
    PRIMARY KEY (Name, Position)
)

;WITH CTE AS (
    SELECT Name, Type, Value, CONVERT(FLOAT,Position) AS Position, 0 AS Level 
    FROM @Temp2 WHERE Type=0
    UNION ALL
    SELECT t1.Name, t2.Type, t2.Value, 
        t1.Position+t2.Position*POWER(CONVERT(FLOAT,0.1),1+t2.Level), 
        t2.Level+1 AS Level
    FROM @Temp2 t1
    INNER JOIN CTE t2 ON t2.Name=t1.Value
    WHERE t1.Type=1
)
INSERT INTO @Temp3
SELECT * FROM CTE

SELECT Value
FROM (
    SELECT Value, 0 AS Extra, Position 
    FROM @Temp3 WHERE Name=@Root
    UNION ALL
    SELECT Value, 1 AS Extra, Position
    FROM @Temp3 WHERE Value NOT IN (
        SELECT Value 
        FROM @Temp3 WHERE Name=@Root
    )
) u
ORDER BY Extra, Position

一些观察结果:

  • 在XML中,应始终引用属性的值

  • 不清楚你想要哪个顺序在给定根的树之外的值(在这个例子中,只有b4不是从A开始的树的一部分;如果有多个像这样的值,在其他多棵树中,不清楚哪个是所需的顺序)

  • 使用更复杂的CTE可以避免使用表变量,但我认为它们有助于提高性能

  • 我假设每个级别最多有10个子节点;如果有更多的子节点,您可以将0.1更改为0.01,例如