This question has been answered in comments already so this is just a compilation with a bit of explanation.
For the experiments we will use this XML:
DECLARE @XML XML =
'<root root_attr="0">
<leaf leaf_attr="1">one</leaf>
<brunch brunch_attr="2">
<leaf leaf_attr="3">three</leaf>
</brunch>
</root>';
And we need to extract the list of the root element attributes: root_attr="0"
.
For the XPath reference we refer to MSDN XPath Syntax guide
So, "/" stands for "child" or "root node" if appears at the start of the pattern, "@" stands for "attribute", "*" stands for "any" and "." stands for "current context". Surely this should give us all the root attributes:
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('/@*') attribute(name);
Instead it gives an error: Top-level attribute nodes are not supported.
There are two types of nodes in XML: <element>Element Value</element> and <element attribute="Attribute Value" />. So, /@*
XPath is interpreted as any attribute for the root of XML, not of the root element. In fact that can be illustrated with:
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('/') attribute(name);
Returning:
Name Value
---- --------
onethree
Which is an anonymous node representing the entire XML document. '.'
XPath would give the same result.
Ok, so we need to specify any element at the root of the XML document. The syntax for that should have been "//" (child of anonymous root node = root element) should this expression not stand for "Recursive descent" (all children). Indeed
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('//@*') attribute(name);
Returns full list of attributes of all elements:
Name Value
----------- --------
root_attr 0
leaf_attr 1
brunch_attr 2
leaf_attr 3
Ok, now we need a way to say in XPath "root" "element" instead of "rootelement" which is apparently a reserved word. One way is to squeeze in "any", the other - to specify that it should be "node()" unless of cause we know the actual name of the root element.
For the given XML those three are equal:
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('/*/@*') attribute(name);
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('/node()/@*') attribute(name);
SELECT
CAST(attribute.name.query('local-name(.)') AS VARCHAR(MAX)) As [Name],
attribute.name.value('.','NVARCHAR(MAX)') As [Value]
FROM @XML.nodes('/root/@*') attribute(name);
Returning:
Name Value
--------- --------
root_attr 0
There we are. A bit of XPath tautology to work around the "//" reserved word.