【问题标题】:How to create hierachical xml generically如何从基因上创建分层 xml
【发布时间】:2018-08-09 11:34:15
【问题描述】:

我需要能够从 SQL 输出分层 XML,但我不想为每个所需的架构创建类似的函数。

例如,给定的数据如下所示:

+----+----------+----------------------------------------------------------+
| ID | ParentID |                           XML                            |
+----+----------+----------------------------------------------------------+
|  1 | NULL     | <root/>                                                  |
|  2 | 1        | <A someattr="whatever">some text</A>                     |
|  3 | 1        | <B someattr="stuff">more text <and><elements/></and></B> |
|  4 | 3        | <B1>Child of B</B1>                                      |
|  5 | 4        | <B1A/>                                                   |
|  6 | 2        | <Child_Of-A/>                                            |
+----+----------+----------------------------------------------------------+

我们应该有一个如下所示的输出:

<root>
  <A someattr="whatever">some text<Child_Of-A /></A>
  <B someattr="stuff">more text<and><elements /></and>
    <B1>Child of B<B1A />
    </B1>
  </B>
</root>

(或语义相同)

互联网上有很多帖子解释如何以非动态方式执行此操作,但我找不到任何通用解决方案。

如何在不为每个架构创建新函数(或一组)的情况下完成?

【问题讨论】:

    标签: sql-server sqlxml


    【解决方案1】:

    事实证明,我们可以分步进行,尽管 N.B.对于大型数据集,这可能非常慢(我的真实数据集只有 191 个节点,仍然需要大约 6 秒)

    对于大型数据集,可能需要一种非动态方法。

    首先让我们创建一个表类型来形式化所需的输入结构。

    CREATE TYPE dbo.HierachicalXMLInput AS TABLE 
    (
      [ID] int,
      [ParentID] int,
      [XML] xml
    )
    

    第一个函数只创建层次结构

    CREATE FUNCTION dbo.fnHierachicalXMLHierachy
    (   @Data dbo.HierachicalXMLInput READONLY,
        @ParentID int = NULL
    )   RETURNS XML AS
    BEGIN
        DECLARE @Out xml
        ;
        WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
        SELECT @Out = ( 
            SELECT [ID] AS [@ID],
                   dbo.fnHierachicalXMLHierachy(@Data, [ID]) AS [*]
            FROM   @Data
            WHERE  ([ParentID]=@ParentID) Or ([ParentID] IS NULL AND @ParentID IS NULL)
            FOR XML PATH ('h:N')
        )
        RETURN @Out
    END
    

    这将输出类似这样的内容(为清楚起见,省略了冗余命名空间声明)

    <h:N xmlns:h="ChangeMetoSomeUniqueNamepsace" ID="1">
      <h:N ID="2">
        <h:N ID="6" />
      </h:N>
      <h:N ID="3">
        <h:N ID="4">
          <h:N  ID="5" />
        </h:N>
      </h:N>
    </h:N>
    

    然后我们可以把它和原始数据结合起来得到需要的输出:

    CREATE FUNCTION dbo.fnHierachicalXML
    (   @Data dbo.HierachicalXMLInput READONLY
    )   RETURNS XML AS
    BEGIN
      DECLARE @XML xml = dbo.fnHierachicalXMLHierachy(@Data, NULL)
      DECLARE @NodeID int 
      -- we need to replace the placeholder nodes one by one.
      -- we should only replace nodes that do not have placeholder children
      -- i.e. we start deepest first
      -- find the first node to replace and get it's ID
      ; WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
      SELECT @NodeID= @XML.value('(//h:N[not(h:N)])[1]/@ID', 'int')
      DECLARE @Original xml
    
      WHILE @NodeID IS NOT NULL -- there is something to replace
      BEGIN
        --get the actual XML that is supposed to go where the placeholder currently is
        SELECT @Original=[XML]FROM  @Data WHERE ID=@NodeID
        -- insert the desired node after the placeholder
        SET @XML.modify('declare namespace h="ChangeMetoSomeUniqueNamepsace";  
          insert sql:variable("@Original")  after (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]')    
        -- if this isn't the first time around then the placeholder's placeholder children have already been replace with actual content.
        -- so insert the placeholder's content into the real node
        SET @XML.modify('
          declare namespace h="ChangeMetoSomeUniqueNamepsace";  
          insert (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]/*  
          as last into 
         ((//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]/../*[. >>
           (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]
         ])[1]
        ')  
        -- in the above: x/../*[. >> x] is basically the same (as x::following-sibling:*)
        -- sadly it seems SQL's XML DML doesn't support axes like following-sibling, though it's not clear why this is so.
        -- Instead we use the more convoluted syntax
    
        -- finally, delete the placeholder
        SET @XML.modify('
          declare namespace h="ChangeMetoSomeUniqueNamepsace";  
          delete (//h:N[@ID=sql:variable("@NodeID") and not(h:N)])[1]
        ')
        -- and find the next placeholder to replace
        ;WITH XMLNAMESPACES ('ChangeMetoSomeUniqueNamepsace' as h)
        SELECT @NodeID = @XML.value('(//h:N[not(h:N)])[1]/@ID', 'int')
      END
    RETURN @XML
    END
    

    那么我们可以这样使用它:

    DECLARE @dt AS dbo.HierachicalXMLInput
    INSERT INTO @dt SELECT 1, NULL, '<root></root>'
    INSERT INTO @dt SELECT 2, 1, '<A someattr="whatever">some text</A>'
    INSERT INTO @dt SELECT 3, 1, '<B someattr="stuff">more text <and><elements/></and></B>'
    INSERT INTO @dt SELECT 4, 3, '<B1>Child of B</B1>'
    INSERT INTO @dt SELECT 5, 4, '<B1A/>'
    INSERT INTO @dt SELECT 6, 2, '<Child_Of-A/>'
    
    -- SELECT * FROM @dt
    
    DECLARE @XML xml = dbo.fnHierachicalXML (@dt)
    SELECT @XML
    

    并获得所需的输出:

    <root>
      <A someattr="whatever">some text<Child_Of-A /></A>
      <B someattr="stuff">more text <and><elements /></and><B1>Child of B<B1A /></B1></B>
    </root>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-15
      • 2021-11-29
      • 1970-01-01
      相关资源
      最近更新 更多