Consequences of redefining the default element namespace

Check out Mike's entry regarding namespaces in XQuery.

I wanted to illustrate one more point regarding the consequences when namespace prefix bindings affect both query and construction. It is about the default element namespace. The real problem hits when you are querying data which is in the empty namespace and you are constructing an instance which overrides the default namespace. Why is this an issue? Because there is no way to bind a prefix to the empty namespace. Once you have redefined the default element namespace, there is no easy or straightforward way to query elements which are in the empty namespace.

Take the following example instance:

declare @x xml
set @x =
N'
<Animals>
<Animal>
<Name>Snake</Name>
<Legs>0</Legs>
</Animal>
<Animal>
<Name>Dog</Name>
<Legs>4</Legs>
</Animal>
</Animals>' ;

Now, what I want to do is construct an element which tells me the average number of legs for all of the animals in the instance. And I want this instance to exist in the namespace "urn-zoo-statistics". Lets give it a shot:

select @x.query('
<AverageLegCount xmlns="urn-zoo-statistics">
{ avg (//Legs) }
</AverageLegCount>
')

Here are the results that I get:

<AverageLegCount xmlns="urn-zoo-statistics" />

Not exactly what I was looking for. What is the problem? Well the xmlns="urn-zoo-statistics" attribute in the element constructor has overridden the default element namespace for the XPath statement that I am passing to avg(). So the XPath statement doesn't return any nodes. Not good times.

There are a couple of ways to fix this. The simplest of which is to simply not bind "urn-zoo-statistics" as the default element namespace, and instead bind it to a prefix. We can modify the query as follows:

select @x.query('
<stat:AverageLegCount xmlns:stat="urn-zoo-statistics">
{ avg (//Legs) }
</stat:AverageLegCount>
')

This now gives us the correct results:

<stat:AverageLegCount xmlns:stat="urn-zoo-statistics">2</stat:AverageLegCount>

Now, lets say for some reason you just have to redefine the default element namespace, and still have to query data which lives in the empty namespace, there is one trick you have left. You can use the namespace-uri() function. I could rewrite the above query, binding "urn-zoo-statistics" to the default element namespace, and still query the Legs element using a wildcard in the XPath statement, and a predicate which checks the namespace that the element belongs to. My "last ditch effort" query would look like this:

select @x.query('
<AverageLegCount xmlns="urn-zoo-statistics">
{ avg (//*:Legs[namespace-uri()=""]) }
</AverageLegCount>
')

And it returns these results:

<AverageLegCount xmlns="urn-zoo-statistics">2</AverageLegCount>