【问题标题】:How do I remove redundant namespace in nested query when using FOR XML PATH使用 FOR XML PATH 时如何删除嵌套查询中的冗余命名空间
【发布时间】:2010-07-13 22:28:44
【问题描述】:

更新:我发现有针对此问题提出的 Microsoft Connect 项目here

当使用FOR XML PATHWITH XMLNAMESPACES 声明默认命名空间时,对于使用 FOR 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')

什么是最好的解决方案?

【问题讨论】:

  • 您能向我们展示您的 FOR XML PATH 查询,以及生成的带有额外命名空间的 XML 吗?它有助于在屏幕上看到这些东西来诊断/建议解决方法......
  • 我添加了一个完整的工作示例。

标签: sql xml sql-server-2008


【解决方案1】:

经过数小时的绝望和数百次尝试和错误,我想出了下面的解决方案。

我遇到了同样的问题,当时我想要 只有一个 xmlns 属性,在 root 节点 only 上。但是我也有一个非常困难的查询,有很多子查询,单独的 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。我们不需要它,所以我们在 FOR XML EXPLICIT 查询中使用 directive xmltext 删除它:

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

虽然 MSDN 中的解释听起来更复杂,但实际上它告诉解析器选择XML 根节点的内容

不确定查询的速度有多快,但目前我正在放松,像绅士一样喝着苏格兰威士忌,同时平静地看着代码……

【讨论】:

  • 就像一个魅力,如果命名空间不起作用,命名空间将被添加......
【解决方案2】:

如果我理解正确,您指的是您可能在这样的查询中看到的行为:

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>

【讨论】:

  • +1 为您的回答喝彩,您有什么想法与替代方案相比? (见我对我的问题的回答)
【解决方案3】:

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

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');

【讨论】:

  • 对于 1m 行,您的解决方案运行速度是原来的两倍。 =( 虽然它在每个“物种”行中都有一个 xmlns=""。这有关系吗?关于 FOR XML EXPLICIT 方法的有趣之处在于它允许您执行多个命名空间。我不确定您会怎么做使用替代解决方案(尽管如果您不需要,可能没关系)。如果您好奇,请在此处链接到性能测试:tinyurl.com/3yejtyv
  • 我已经更改了结果以删除空白命名空间,但最终无论哪种方式,我猜这种方法都会生成漂亮的 xml,但它并不是真正有效的。所以我接受了 8kb 的答案作为最好的方法。
  • DEFAULT 命名空间在第一个子节点中添加空 xmlns
【解决方案4】:

当使用 XML PATH 时,您不能直接手动声明命名空间,这使问题更加复杂。 SQL Server 将禁止任何以“xmlns”开头的属性名称和任何带有冒号的标记名称。

我不必求助于使用相对不友好的 XML EXPLICIT,而是通过首先生成带有“隐藏”命名空间定义和引用的 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 标识符的内容,您就可以将其替换为其他内容。

  2. 由于我们希望在顶层定义 xmlns,我们不能对 XML PATH 使用“ROOT”选项——相反,我需要在子选择结构中添加另一个外部级别来实现这一点。

【讨论】:

  • 这绝对是对这个问题的深思熟虑的回应。然而,这个问题本身是 6 年前 前提出的,这里提供的答案应该被新读者视为对可能长期存在的问题的最新解决方案。
  • @Claies - 是的,绝对 - 我当然不希望 OP 在这么久之后对我的答案感兴趣! - 它绝对是针对其他寻找这个问题的解决方案的读者(就像我一样),据我所知,它仍然会出现在 SQL Server 2016 中。
  • 我现在忘记了 XML 是什么,抱歉! (无论如何,+1 以获得高质量的答案!)
  • 这样的 XML 字符串操作是不安全的。如果您在某个值的文本节点中有字符串 xxmlns.. 怎么办?例如,如果您在处理这个 stackoverflow 答案的 XML 导出的代码中运行它怎么办?您至少确实提到了这一点,但我认为,即便如此,由于这个问题,这个答案不应该被认为是有效的。
【解决方案5】:

在手动声明“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>

希望这将有助于人们在网上搜索类似问题。 ;-)

【讨论】:

  • 太棒了!所以不明显的技巧是 root 元素需要使用for xml raw 选择,但内部元素仍然可以使用for xml path
  • 实际上只有设置非默认xmlns:og才有效,如果你想设置默认xmlns,那么你仍然会得到重复的命名空间声明。
【解决方案6】:

如果 FOR XML PATH 真的能更干净地工作,那就太好了。使用 @table 变量重做您的原始示例:

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');

通过重复的命名空间声明产生问题 XML:

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

【讨论】:

    猜你喜欢
    • 2016-12-27
    • 1970-01-01
    • 2015-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多