SQLXML - Search and Query node element?
I have an XML like this stored in an XML datatype column (will have multiple such rows in table)-
<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>
How can I filter based on a node element say (using SQL SERVER 2008 R2) - get all 'Elem1' nodes or get all 'Name' nodes or get all TimeZone nodes ? Something like using local-name() function ?
EDIT - Part Solution -
I got the solution partly (see John's reply below and then run this) -
SELECT C1.query('fn:local-name(.)') AS Nodes FROM [dbo].[MyXmlTable] AS MyXML CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T ( C1 )
The query above returns al开发者_如何学JAVAl the node elements across the TABLE. Now, I want to say filter upon specific elements and return the element and its value or its attribute value. How to achieve this (by using WHERE clause or any other filter mechanism)?
I'm not sure what result you are looking for but something like this perhaps.
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)
Result:
<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>
Edit
If you want the actual value instead of the entire XML you can do like this instead.
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)
Result:
Value
------------------
GMT Standard Time
You can transform XML into table like here:
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
Result:
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
Later you can query this result to get node/attribute value which do you need.
But it works only in your example, when you have only one node and all names are unique. In multinode XML you should use hierarchical numbering like '1-1-2' or something like this. It is much more complicated and i do not suggest to going this way.
It's not clear to me exactly what your output should look like. However, this should get you started:
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
This queries the XML for the "Name" elements -- you can modify the query depending on what kind of output you want exactly. It's a bit long, but the MSDN article on SQLXML is pretty informative:
http://msdn.microsoft.com/en-us/library/ms345117(v=sql.90).aspx
Hope this helps!
John
Update: you can add a where clause something like this. I'm still not clear on what you want the output to look like, but this will filter out the "Elem1" values:
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'
One more update; hopefully this is the answer you are looking for!
Try using a wildcard in the query. I had to use dynamic SQL because the XML query() function will only take string literals for paths (you can use sql:variable("@filter") for values, but I wasn't able to get that working for a path.)
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
This will return the Elem1 XML (and all sub-nodes):
<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>
And if you want to pick out "TimeZone" you would do this:
SET @filter = '*/*/TimeZone'
精彩评论