提问人:Dog Ears 提问时间:7/14/2010 最后编辑:Luke GirvinDog Ears 更新时间:7/29/2019 访问量:19024
使用 FOR XML PATH 时如何删除嵌套查询中的冗余命名空间
How do I remove redundant namespace in nested query when using FOR XML PATH
问:
更新:我发现这里有一个针对此问题提出的 Microsoft Connect 项目
当使用 and 声明默认命名空间时,我将为使用 FOR XML 的嵌套查询在任何顶级节点中复制命名空间声明,我偶然发现了一些在线解决方案,但我并不完全相信......FOR XML PATH
WITH XMLNAMESPACES
这是一个完整的示例
/*
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')
最好的解决方案是什么?
答:
如果我理解正确,您指的是在查询中可能看到的行为,如下所示:
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(网站现已解散)上的对话,此行为是一项功能,尽管可以选择投票更改它。
我建议的解决方案是恢复到: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>
评论
我见过的另一种解决方案是在将 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');
评论
我对所有这些解释有点困惑,而手动声明“xmlns:animals”正在做这项工作: 这是我写的一个示例,用于生成 Open graph 元数据
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 & 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>
希望这将有助于人们在网上搜索类似的问题。;-)
评论
for xml raw
for xml path
xmlns:og
xmlns
在使用 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
需要指出的几点:
我使用“xxmlns”作为“xmlns”的隐身版本,而“..”来代替“:”。如果您可能将“..”作为文本值的一部分,这可能不适合您 - 只要您选择构成有效 XML 标识符的内容,就可以将其替换为其他内容。
由于我们希望在顶层使用 xmlns 定义,因此我们不能将“ROOT”选项用于 XML PATH - 相反,我需要在子选择结构中添加另一个外部级别来实现这一点。
评论
xxmlns
..
NONAMESPACE
FOR XML PATH
FOR XML PATH
经过几个小时的绝望和数百次的试验和错误,我想出了下面的解决方案。
当我只想要一个属性时,我遇到了同样的问题,仅在根节点上。但是我也有一个非常困难的查询,有很多子查询,仅靠方法就太麻烦了。所以是的,我想要在子查询中的便利性,并设置我自己的.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
xmltext
FOR XML EXPLICIT
,@xml AS [xml!1!!xmltext]
虽然MSDN中的解释听起来比较复杂,但实际上它告诉解析器选择根节点的内容。XML
不知道查询的速度有多快,但目前我正在放松,像绅士一样喝苏格兰威士忌,同时平静地看着代码......
评论
如果 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>
您可以使用具有通配符命名空间匹配的 XQuery 在命名空间之间迁移元素(即 *:elementName),如下所示,但对于复杂的 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>
评论