2011-08-02 51 views
1

我有这样存储在XML数据类型列的XML(将在表中的多个这样的行) -SQLXML - 搜索和查询节点元素?

<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> 

我如何进行筛选说(使用SQL Server 2008 R2)基于节点元素 - 获得所有'Elem1'节点或获取所有'Name'节点或获得所有TimeZone节点?就像使用local-name()函数一样?

编辑 - 部件解决方案 -

我得到了解决部分(见下文约翰的答复,然后运行这个) -

SELECT C1.query('fn:local-name(.)') AS Nodes FROM [dbo].[MyXmlTable] AS MyXML CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T (C1) 

上面的查询返回跨越所有节点元素表。现在,我想对特定元素进行过滤,并返回元素及其值或属性值。如何实现这一点(通过使用WHERE子句或任何其他过滤机制)?

回答

1

我不确定你在找什么结果f或者可能是这样的事情。

declare @T table(XMLCol xml) 
insert into @T values 
('<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root>') 

declare @Node varchar(50) 
set @Node = 'Elem1' 

select N.query('.') as Value 
from @T as T 
    cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N) 

结果:

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1"> 
    <p1:Name type="string" display="First name">John</p1:Name> 
    <p1:TimeZone display="Time zone"> 
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName> 
    </p1:TimeZone> 
</p1:Elem1> 

编辑

如果你想实际值,而不是整个XML,你可以像这样来代替。

declare @Node varchar(50) 
set @Node = 'TimeZone' 

select N.value('.', 'varchar(100)') as Value 
from @T as T 
    cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("@Node")]') as X(N) 

结果:

Value 
------------------ 
GMT Standard Time 
+0

结果应该只有值'GMT标准时间',输入将是'TimeZone'节点元素 –

+0

@SeeSharp - 已更新的答案。如果你总是想查询“TimeZone”,你只需用'TimeZone'替换'sql:variable(“@ Node”)''。 –

+0

谢谢Mikael! –

0

我不清楚你的输出应该是什么样子。然而,这应该让你开始:

create table MyXmlTable (MyXmlCol xml) 
insert into MyXmlTable (MyXmlCol) values 
(
' 
<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
    <Elem1 type="T2"> 
    <Name type="string" display="First name">Fred</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">EST Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> 
'); 

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org') 
select MyXmlCol.query('/Root/Elem1/Name') 
from MyXmlTable 

这将查询“名称”元素XML - 你可以修改这取决于你想要什么样的输出的查询。这是一个有点长,但SQLXML MSDN文章是相当翔实:

http://msdn.microsoft.com/en-us/library/ms345117(v=sql.90).aspx 

希望这有助于!

约翰

更新:你可以添加一个where子句这样的东西。我还没有你想要的输出是什么样子清楚,但是这样会过滤掉“Elem1”值:

SELECT C1.query('fn:local-name(.)') AS Nodes 
FROM [dbo].[MyXmlTable] AS MyXML 
CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T (C1) 
WHERE CAST(C1.query('fn:local-name(.)') AS NVARCHAR(32)) <> 'Elem1' 

还有一个更新;希望这是你正在寻找的答案!

尝试在查询中使用通配符。我不得不使用动态SQL,因为XML查询()函数只会为路径使用字符串文字(可以使用sql:variable(“@ filter”)作为值,但我无法获得路径的效果。 )

DECLARE @filter nvarchar(20) 
SET @filter = '*/Elem1' 

DECLARE @sqlCommand nvarchar(1000) 
SET @sqlCommand = 
    ';WITH XMLNAMESPACES(DEFAULT ''http://tempuri.org'') 
    select MyXmlCol.query(''' + @filter + ''') 
    from MyXmlTable' 
print @sqlCommand 
EXECUTE sp_executesql @sqlCommand, N'@filter nvarchar(20)', @filter = @filter 

这将返回Elem1 XML(和所有子节点):

<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1"> 
    <p1:Name type="string" display="First name">John</p1:Name> 
    <p1:TimeZone display="Time zone"> 
    <p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName> 
    </p1:TimeZone> 
</p1:Elem1> 
<p2:Elem1 xmlns:p2="http://tempuri.org" type="T2"> 
    <p2:Name type="string" display="First name">Fred</p2:Name> 
    <p2:TimeZone display="Time zone"> 
    <p2:DisplayName type="string" display="Display name">EST Standard Time</p2:DisplayName> 
    </p2:TimeZone> 
</p2:Elem1> 

如果你想挑选出 “时区”,你这样做:

SET @filter = '*/*/TimeZone' 
+0

这很有用。谢谢。但是,用户可以给任何节点元素作为输入变量,如'Elem1',并且基于此我必须过滤出该节点元素及其相应的值。我不知道Elem1或XML中的任何元素在哪里。如果找不到节点元素,则返回错误。请注意:Elem1或Name元素可以成为表中多行的XML列的一部分 –

+0

我看到了您的更新,我仍然有点不清楚您希望输出的样子。你能修改你的查询来删除某些结果吗?让我发布我的答案更新。 – JohnD

+0

输入将是任何节点元素,输出应该是相应的节点元素及其值(全部在T-SQL中,因为这应该作为视图的一部分存储)。希望我清楚。 –

0

您可以将XML转换为表喜欢这里:

declare @XML xml='<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <Elem1 type="T1"> 
    <Name type="string" display="First name">John</Name> 
    <TimeZone display="Time zone"> 
     <DisplayName type="string" display="Display name">GMT Standard Time</DisplayName> 
    </TimeZone> 
    </Elem1> 
</Root> ' 

;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org'), 
numbers as(
SELECT ROW_NUMBER() OVER(ORDER BY o1.object_id,o2.object_id) Num 
FROM sys.objects o1 CROSS JOIN sys.objects o2), 
c as(
SELECT 
b.value('local-name(.)','nvarchar(1000)') Node_Name, 
b.value('./text()[1]','nvarchar(1000)') Node_Value, 
b.value('count(@*)','nvarchar(MAX)') AttributeCount, 
Num Attribute_Number 
FROM 
@xml.nodes('Root//*') a(b) 
CROSS APPLY Numbers 
WHERE Num<=b.value('count(@*)','nvarchar(MAX)') 
) 
SELECT c.Node_Name,c.node_Value,Attribute_Number, 
    @XML.query('for $Attr in //*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")] return local-name($Attr)').value('.','nvarchar(MAX)') Attribute_Name, 
    @XML.value('data(//*/.[local-name(.)=sql:column("Node_Name")]/@*[sql:column("Attribute_Number")])[1]','nvarchar(1000)') Attribute_Value 
FROM c 

结果:

Node_Name node_Value   Attribute_Number Attribute_Name Attribute_Value 
Elem1  NULL      1    type    T1 
Name   John      1    type   string 
Name   John      2    display  First name 
TimeZone  NULL      1    display  Time zone 
DisplayName GMT Standard Time   1    type   string 
DisplayName GMT Standard Time   2    display  Display name 

以后可以曲这个结果可以获得你需要的节点/属性值。

但是它只适用于您的示例,当您只有一个节点并且所有名称都是唯一的。在多节点XML中,您应该使用分层编号,如“1-1-2”或类似的东西。这更复杂,我不建议这样去。