【问题标题】:Self-joined data to hierarchical XML自联接数据到分层 XML
【发布时间】:2014-02-25 00:33:37
【问题描述】:

我在 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>

编辑 2:

这是我用来生成上述结果的代码(米奇解决方案的略微修改版本):

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

【问题讨论】:

    标签: sql-server xml recursive-query


    【解决方案1】:

    您可以通过深度优先搜索来进行此类翻译。使用递归 CTE 计算深度,然后使用循环转换为 XML:

    Example SQL (Fiddle):

    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>
    

    【讨论】:

    • 这似乎是一个很好的解决方案,但是当我使用我的示例数据运行它时,我得到了一些重复的行。我试图自己解决这个问题,但我仍然做不到。你能帮我吗?有关详细信息,请参阅编辑。
    • 把它放在sqlfiddle.com 或者发布你的SQL。就目前而言,我看到了副本,但无法说出您是如何生成它们的。
    • 很抱歉。我再次编辑了问题并添加了我修改后的代码版本。谢谢!
    • 看起来你有一个图而不是一棵树,所以一个节点不能被唯一标识。使用父 → 子关系来唯一标识一个节点,它可以工作(n.leftid = xl.idn.leftid = xl.id and n.id = xl.parent)。见sqlfiddle.com/#!6/1c983/7。我还更新了我的示例。
    • 那是完美的。非常感谢!
    【解决方案2】:

    好吧,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   
    

    【讨论】:

      猜你喜欢
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-03
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 2010-11-12
      • 2011-11-12
      相关资源
      最近更新 更多