Why does .value('@foo', 'varchar(20)')
generate the error “Top-level attribute nodes are not supported”?
When you query the xml
data type, the context is the document node, which is an implicit node that contains the root element(s) of your XML document. The document node has no name and no attributes.
How can I get the value of an attribute on the root element?
In your XQuery expression, include the path to the first root element:
DECLARE @Data xml = '<Customer ID="123"><Order ID="ABC" /></Customer>'
SELECT @Data.value('Customer[1]/@ID', 'varchar(20)')
-- Result: 123
If you don’t know (or don’t want to specify) the name of the root element, then just use *
to match any element:
SELECT @Data.value('*[1]/@ID', 'varchar(20)')
-- Result: 123
Because the query context is the document node, you don’t need to prefix the XQuery expression with a forward slash (as the other answers unnecessarily do).
Why do I have to include [1]
?
The XQuery expression you pass to value()
must be guaranteed to return a singleton. The expression Customer/@ID
doesn’t satisfy this requirement because it matches both ID="123"
and ID="456"
in the following example:
DECLARE @Data xml = '<Customer ID="123" /><Customer ID="456" />'
Remember that the xml
data type represents an XML document fragment, not an XML document, so it can contain multiple root elements.
What’s the difference between Customer[1]/@ID
and (Customer/@ID)[1]
?
The expression Customer[1]/@ID
retrieves the ID
attribute of the first <Customer>
element.
The expression (Customer/@ID)[1]
retrieves the ID
attribute of all <Customer>
elements, and from this list of attributes, picks the first.
The following example demonstrates the difference:
DECLARE @Data xml = '<Customer /><Customer ID="123" /><Customer ID="456" />'
SELECT @Data.value('Customer[1]/@ID', 'varchar(20)')
-- Result: NULL (because the first Customer element doesn't have an ID attribute)
SELECT @Data.value('(Customer/@ID)[1]', 'varchar(20)')
-- Result: 123