views:

31

answers:

2

I have an xml column and I want to persist a node count in it in an adjacent column.

For example, in an xml given below, I have 3 nodes a and so I want to output it.

The problem that I have, however, is that I can't manage to create a generic schema-bound function that would take @xml and @nodeName so as to return the count of a specified node.

create function dbo.fnXmlCount_a (@xml xml) 
returns int with schemabinding
as begin
return (@xml.value('count(//a)', 'int'))
end

declare @xml xml;
set @xml = '
<r>
 <a></a>
 <b></b>
 <a></a>
 <c>
  <a></a> 
 </c>
</r>'

select dbo.fnXmlCount_a(@xml) aCount

Result of the last operation is:

aCount
3

And then I create a column on that table that has this xml field like so:

alter table [XmlTable] add column [aCount] as (dbo.fnXmlCount_a([xml])) persisted

But what I really would like to do is this:

alter table [XmlTable] add column [aCount] as (dbo.fnXmlNodeCount([xml], 'a')) persisted

Thanks!

Update:

We all know that // is really slow, so it would be great if you could figure out how to pass this path to search in in another variable; by default, this searching path would be //.

+1  A: 

Here you go:

create function dbo.fnXmlCount (@xml xml, @name nvarchar(max))  
returns int with schemabinding 
as begin 
declare @count int
select @count = count(*)
from @xml.nodes('//*') as t(c)
where t.c.value('local-name(.)', 'nvarchar(max)') = @name
return @count
end 
Daniel Renshaw
Are you serious?
Denis Valeev
This approach took 46 seconds to count nodes in my table while my initial hard-coded approach took about 6 seconds to complete. But it definitely works! :) But the performance is terrible, I'm going to play with it to see if I can improve performance.
Denis Valeev
Yeah, it will be a lot slower
Daniel Renshaw
An XML index might help. The speed issue is probably not from using `//`, it's from using both `nodes` and then `value` over every resulting node.
Daniel Renshaw
@Daniel Renshaw, believe it or not but with the proper path specified it takes 11 seconds to complete
Denis Valeev
@Daniel Renshaw, you may be interested to see in my improvement over your solution.
Denis Valeev
+1  A: 

I have come up with a little better solution but the approach is similar to that of Daniel Renshaw.

create function [dbo].[fnXmlCount] (@xml xml, @nodeName nvarchar(max))  
returns int with schemabinding 
as begin 
return @xml.value('count(//*[local-name() = sql:variable("@nodeName")])', 'int')
end 

However, there's still no solution as to how to pass the path to nodes being counted.

And it takes about 20 seconds to count nodes on my xml table.

Denis Valeev
+1, nice approach; this should be much faster than mine
Daniel Renshaw