代码之家  ›  专栏  ›  技术社区  ›  Dog Ears

使用FOR XML PATH时,如何删除嵌套查询中的冗余命名空间

  •  16
  • Dog Ears  · 技术社区  · 14 年前

    :我发现此问题引发了一个Microsoft Connect项目 here

    使用时 FOR XML PATH WITH XMLNAMESPACES 为了声明一个默认名称空间,我将在用于XML的嵌套查询的任何顶层节点中复制名称空间声明,我在网上偶然发现了一些解决方案,但我并不完全相信。。。

    下面是一个完整的例子

    /*
    drop table t1
    drop table t2
    */
    create table t1 ( c1 int, c2 varchar(50))
    create table t2 ( c1 int, c2 int, c3 varchar(50))
    insert t1 values 
    (1, 'Mouse'),
    (2, 'Chicken'),
    (3, 'Snake');
    insert t2 values
    (1, 1, 'Front Right'),
    (2, 1, 'Front Left'),
    (3, 1, 'Back Right'),
    (4, 1, 'Back Left'),
    (5, 2, 'Right'),
    (6, 2, 'Left')
    
    
    
    ;with XmlNamespaces( default 'uri:animal')
    select 
        a.c2 as "@species"
        , (select l.c3 as "text()" 
           from t2 l where l.c2 = a.c1 
           for xml path('leg'), type) as "legs"
    from t1 a
    for xml path('animal'), root('zoo')
    

    最好的解决方案是什么?

    5 回复  |  直到 5 年前
        1
  •  11
  •   8kb    8 年前

    DECLARE @Order TABLE (
      OrderID INT, 
      OrderDate DATETIME)
    
    DECLARE @OrderDetail TABLE (
      OrderID INT, 
      ItemID VARCHAR(1), 
      ItemName VARCHAR(50), 
      Qty INT)
    
    INSERT @Order 
    VALUES 
    (1, '2010-01-01'),
    (2, '2010-01-02')
    
    INSERT @OrderDetail 
    VALUES 
    (1, 'A', 'Drink',  5),
    (1, 'B', 'Cup',    2),
    (2, 'A', 'Drink',  2),
    (2, 'C', 'Straw',  1),
    (2, 'D', 'Napkin', 1)
    
    ;WITH XMLNAMESPACES('http://test.com/order' AS od) 
    SELECT
      OrderID AS "@OrderID",
      (SELECT 
         ItemID AS "@od:ItemID", 
         ItemName AS "data()" 
       FROM @OrderDetail 
       WHERE OrderID = o.OrderID 
       FOR XML PATH ('od.Item'), TYPE)
    FROM @Order o 
    FOR XML PATH ('od.Order'), TYPE, ROOT('xml')
    

    <xml xmlns:od="http://test.com/order">
      <od.Order OrderID="1">
        <od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item>
        <od.Item xmlns:od="http://test.com/order" od:ItemID="B">Cup</od.Item>
      </od.Order>
      <od.Order OrderID="2">
        <od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item>
        <od.Item xmlns:od="http://test.com/order" od:ItemID="C">Straw</od.Item>
        <od.Item xmlns:od="http://test.com/order" od:ItemID="D">Napkin</od.Item>
      </od.Order>
    </xml>
    

    正如您所说,子查询的结果中重复了名称空间。

    根据devnetnewsgroup(网站现已关闭)上的对话,这种行为是一种特性,尽管可以选择 vote

    我建议的解决办法是回到 FOR XML EXPLICIT

    SELECT
      1 AS Tag,
      NULL AS Parent,
      'http://test.com/order' AS [xml!1!xmlns:od],
      NULL AS [od:Order!2],
      NULL AS [od:Order!2!OrderID],
      NULL AS [od:Item!3],
      NULL AS [od:Item!3!ItemID]
    UNION ALL
    SELECT 
      2 AS Tag,
      1 AS Parent,
      'http://test.com/order' AS [xml!1!xmlns:od],
      NULL AS [od:Order!2],
      OrderID AS [od:Order!2!OrderID],
      NULL AS [od:Item!3],
      NULL [od:Item!3!ItemID]
    FROM @Order 
    UNION ALL
    SELECT
      3 AS Tag,
      2 AS Parent,
      'http://test.com/order' AS [xml!1!xmlns:od],
      NULL AS [od:Order!2],
      o.OrderID AS [od:Order!2!OrderID],
      d.ItemName AS [od:Item!3],
      d.ItemID AS [od:Item!3!ItemID]
    FROM @Order o INNER JOIN @OrderDetail d ON o.OrderID = d.OrderID
    ORDER BY [od:Order!2!OrderID], [od:Item!3!ItemID]
    FOR XML EXPLICIT
    

    看看这些结果:

    <xml xmlns:od="http://test.com/order">
      <od:Order OrderID="1">
        <od:Item ItemID="A">Drink</od:Item>
        <od:Item ItemID="B">Cup</od:Item>
      </od:Order>
      <od:Order OrderID="2">
        <od:Item ItemID="A">Drink</od:Item>
        <od:Item ItemID="C">Straw</od:Item>
        <od:Item ItemID="D">Napkin</od:Item>
      </od:Order>
    </xml>
    
        2
  •  12
  •   Gabrielius    7 年前

    经过数小时的绝望和数百次的审判;错误,我提出了下面的解决方案。

    我也有同样的问题,当我想要的时候 就一个 xmlns 属性,在 节点 只有 FOR XML EXPLICIT 光是方法就太麻烦了。所以是的,我想要 FOR XML PATH 在子查询中也可以设置我自己的 xmlns公司 .

    8kb的 回答,因为那太好了。为了更好的理解,我对它做了一些调整。代码如下:

    DECLARE @Order TABLE (OrderID INT, OrderDate DATETIME)    
    DECLARE @OrderDetail TABLE (OrderID INT, ItemID VARCHAR(1), Name VARCHAR(50), Qty INT)    
    INSERT @Order VALUES (1, '2010-01-01'), (2, '2010-01-02')    
    INSERT @OrderDetail VALUES (1, 'A', 'Drink',  5),
                               (1, 'B', 'Cup',    2),
                               (2, 'A', 'Drink',  2),
                               (2, 'C', 'Straw',  1),
                               (2, 'D', 'Napkin', 1)
    
    -- Your ordinary FOR XML PATH query
    DECLARE @xml XML = (SELECT OrderID AS "@OrderID",
                            (SELECT ItemID AS "@ItemID", 
                                    Name AS "data()" 
                             FROM @OrderDetail 
                             WHERE OrderID = o.OrderID 
                             FOR XML PATH ('Item'), TYPE)
                        FROM @Order o 
                        FOR XML PATH ('Order'), ROOT('dummyTag'), TYPE)
    
    -- Magic happens here!       
    SELECT 1 AS Tag
          ,NULL AS Parent
          ,@xml AS [xml!1!!xmltext]
          ,'http://test.com/order' AS [xml!1!xmlns]
    FOR XML EXPLICIT
    

    结果:

    <xml xmlns="http://test.com/order">
      <Order OrderID="1">
        <Item ItemID="A">Drink</Item>
        <Item ItemID="B">Cup</Item>
      </Order>
      <Order OrderID="2">
        <Item ItemID="A">Drink</Item>
        <Item ItemID="C">Straw</Item>
        <Item ItemID="D">Napkin</Item>
      </Order>
    </xml>
    

    如果您选择 @xml 单独地,您将看到它包含根节点 dummyTag . 我们不需要它,所以我们用 directive xmltext 对于XML显式

    ,@xml AS [xml!1!!xmltext]
    

    虽然MSDN中的解释听起来更复杂,但实际上它告诉解析器选择 目录 属于 XML

    不知道有多快的查询,但目前我正在放松和喝苏格兰威士忌像一个绅士,而和平地看着代码。。。

        3
  •  3
  •   Dog Ears    14 年前

    我看到的另一种解决方案是添加 XMLNAMESPACES 将xml构建到临时变量后的声明:

    declare @xml as xml;
    select @xml = (
    select 
        a.c2 as "@species"
        , (select l.c3 as "text()" 
           from t2 l where l.c2 = a.c1 
           for xml path('leg'), type) as "legs"
    from t1 a
    for xml path('animal'))
    
    ;with XmlNamespaces( 'uri:animal' as an)
    select @xml for xml path('') , root('zoo');
    
        4
  •  1
  •   David Claughton    8 年前

    在使用xmlpath时,您不能直接手动声明名称空间,这使得这里的问题更加复杂。SQL Server将不允许任何以“xmlns”开头的属性名和任何带有冒号的标记名。

    我不必求助于使用相对不友好的XML显式方法,而是先用“隐藏”的名称空间定义和引用生成XML,然后按如下所示进行字符串替换。。。

    DECLARE @Order TABLE (
      OrderID INT, 
      OrderDate DATETIME)
    
    DECLARE @OrderDetail TABLE (
      OrderID INT, 
      ItemID VARCHAR(1), 
      ItemName VARCHAR(50), 
      Qty INT)
    
    INSERT @Order 
    VALUES 
    (1, '2010-01-01'),
    (2, '2010-01-02')
    
    INSERT @OrderDetail 
    VALUES 
    (1, 'A', 'Drink',  5),
    (1, 'B', 'Cup',    2),
    (2, 'A', 'Drink',  2),
    (2, 'C', 'Straw',  1),
    (2, 'D', 'Napkin', 1)
    
    declare @xml xml
    
    set @xml = (SELECT
      'http://test.com/order' as "@xxmlns..od",  -- 'Cloaked' namespace def
      (SELECT OrderID AS "@OrderID", 
        (SELECT 
          ItemID AS "@od..ItemID", 
          ItemName AS "data()" 
         FROM @OrderDetail 
         WHERE OrderID = o.OrderID 
         FOR XML PATH ('od..Item'), TYPE)
       FROM @Order o
       FOR XML PATH ('od..Order'), TYPE)
      FOR XML PATH('xml'))
    
    set @xml = cast(replace(replace(cast(@xml as nvarchar(max)), 'xxmlns', 'xmlns'),'..',':') as xml)
    
    select @xml
    

    1. 我使用“xxmlns”作为“xmlns”的隐形版本,用“…”代替“:”。如果可能将“…”作为文本值的一部分,则这可能不适用于您—您可以用其他内容替换它,只要您选择了构成有效XML标识符的内容。

        5
  •  0
  •   user996758 user996758    13 年前

    我对这些解释有点困惑xmlns:animals“手动执行任务: 下面是我编写的一个生成开放图元数据的示例

    DECLARE @l_xml as XML;
    SELECT @l_xml = 
    (
    SELECT 'http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#' as 'xmlns:og',
        (SELECT
            (SELECT 'og:title' as 'property', title as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:type' as 'property', OpenGraphWebMetadataTypes.name as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:image' as 'property', image as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:url' as 'property', url as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:description' as 'property', description as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:site_name' as 'property', siteName as 'content' for xml raw('meta'), TYPE),
            (SELECT 'og:appId' as 'property', appId as 'content' for xml raw('meta'), TYPE)
         FROM OpenGraphWebMetaDatas INNER JOIN OpenGraphWebMetadataTypes ON OpenGraphWebMetaDatas.type = OpenGraphWebMetadataTypes.id WHERE THING_KEY = @p_index 
         for xml path('header'), TYPE),
         (SELECT '' as 'body' for xml path(''), TYPE)
         for xml raw('html'), TYPE
    )
    
    RETURN @l_xml 
    

    返回预期结果

    <html xmlns:og="http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#">
    <header>
    <meta property="og:title" content="The First object"/>
    <meta property="og:type" content="scanilike:tag"/>
    <meta property="og:image" content="http://www.mygeolive.com/images/facebook/facebook-logo.jpg"/>
    <meta property="og:url" content="http://www.scanilike.com/opengraph?id=1"/>
    <meta property="og:description" content="This is the very first object created using the IOThing &amp; ScanILike software. We keep it in file for history purpose. "/>
    <meta property="og:site_name" content="http://www.scanilike.com"/>
    <meta property="og:appId" content="200270673369521"/>
    </header>
    <body/>
    </html>
    

    希望这能帮助人们在网上搜索类似的问题

        6
  •  0
  •   AlwaysLearning    5 年前

    如果FOR-XML路径真的工作得更干净,那就太好了。使用@table variables重写原始示例:

    declare @t1 table (c1 int, c2 varchar(50));
    declare @t2 table (c1 int, c2 int, c3 varchar(50));
    insert @t1 values 
        (1, 'Mouse'),
        (2, 'Chicken'),
        (3, 'Snake');
    insert @t2 values
        (1, 1, 'Front Right'),
        (2, 1, 'Front Left'),
        (3, 1, 'Back Right'),
        (4, 1, 'Back Left'),
        (5, 2, 'Right'),
        (6, 2, 'Left');
    
    ;with xmlnamespaces( default 'uri:animal')
    select  a.c2 as "@species",
        (
            select  l.c3 as "text()"
            from    @t2 l
            where   l.c2 = a.c1
            for xml path('leg'), type
        ) as "legs"
    from @t1 a
    for xml path('animal'), root('zoo');
    

    <zoo xmlns="uri:animal">
      <animal species="Mouse">
        <legs>
          <leg xmlns="uri:animal">Front Right</leg>
          <leg xmlns="uri:animal">Front Left</leg>
          <leg xmlns="uri:animal">Back Right</leg>
          <leg xmlns="uri:animal">Back Left</leg>
        </legs>
      </animal>
      <animal species="Chicken">
        <legs>
          <leg xmlns="uri:animal">Right</leg>
          <leg xmlns="uri:animal">Left</leg>
        </legs>
      </animal>
      <animal species="Snake" />
    </zoo>
    

    您可以使用具有通配符命名空间匹配(即,*:elementName)的XQuery在命名空间之间迁移元素,如下所示,但对于复杂的XML来说,这可能相当麻烦:

    ;with xmlnamespaces( default 'http://tempuri.org/this/namespace/is/meaningless' )
    select (
        select  a.c2 as "@species",
            (
                select  l.c3 as "text()"
                from    @t2 l
                where   l.c2 = a.c1
                for xml path('leg'), type
            ) as "legs"
        from @t1 a
        for xml path('animal'), root('zoo'), type
    ).query('declare default element namespace "uri:animal";
    <zoo>
    { for $a in *:zoo/*:animal return
        <animal>
        {attribute species {$a/@species}}
        { for $l in $a/*:legs return
            <legs>
            { for $m in $l/*:leg return
                <leg>{ $m/text() }</leg>
            }</legs>
        }</animal>
    }</zoo>');
    

    从而产生您想要的结果:

    <zoo xmlns="uri:animal">
      <animal species="Mouse">
        <legs>
          <leg>Front Right</leg>
          <leg>Front Left</leg>
          <leg>Back Right</leg>
          <leg>Back Left</leg>
        </legs>
      </animal>
      <animal species="Chicken">
        <legs>
          <leg>Right</leg>
          <leg>Left</leg>
        </legs>
      </animal>
      <animal species="Snake" />
    </zoo>