Search code examples
sqlsql-serverxmlxpathsqlxml

Retrieve data from a For XML subquery


I am creating a table as follows:

CREATE TABLE dbo.Test
(
  A int,
  B int
)

GO

INSERT INTO Test VALUES (1, 11)
GO

INSERT INTO Test VALUES (5, 55)
GO

INSERT INTO Test VALUES (4, 44)
GO

I have a query which converts this into XML as :

  SELECT A,B
  FROM Test
  ORDER BY A
  FOR XML AUTO, ROOT ('myroot'), ELEMENTS

I need to use the above query as a subquery to get the following result:

A           B
1           11
4           44
5           55

I am trying a query like this but it gives an error:

SELECT Z.Value('@A', 'INT'),
   Z.Value('@B', 'INT')
FROM (SELECT A, B
  FROM Test
  ORDER BY A
  FOR XML AUTO,Elements, ROOT ('myroot')) Doc(Z)

Msg 4121, Level 16, State 1, Line 1 Cannot find either column "Z" or the user-defined function or aggregate "Z.Value", or the > name is ambiguous.

I can write a simple query like below to get the result but the requirement is that I have to convert it into XMl and then retrieve the same result from it using the subquery. Select * from test order by A

I know that I can insert the records returned by For XML in a table variable and then use Cross apply to fetch the result but as said above, I am looking to get this done in a single query without any temporary table or temporary variable.


Solution

  • There're a several issues here. First, your xml looks like this:

    <myroot>
        <Test>
             <A>1</A><B>11</B>
        </Test>
        <Test>
             <A>4</A><B>44</B>
        </Test>
        <Test>
             <A>5</A><B>55</B>
        </Test>
    </myroot>
    

    And you're trying to fetch data as attributes (@A, @B). You need to fetch it as elements (A[1] or (A/text())[1]).

    Second, you have to use type keyword if you want your xml to be xml type. Third, to split data by rows you need nodes() function. So your query becomes:

    select
       D.Z.value('(A/text())[1]', 'int'),
       D.Z.value('(B/text())[1]', 'int')
    from (
       select A, B
       from Test
       order by A
       for xml auto, elements, root('myroot'), type
    ) as Doc(Z)
        outer apply Doc.Z.nodes('myroot/Test') as D(Z)
    

    BTW, I'd better to use attributes, like this:

    select
       D.Z.value('@A', 'int'),
       D.Z.value('@B', 'int')
    from (
       select A, B
       from Test
       order by A
       for xml raw('Test'), root('myroot'), type
    ) as Doc(Z)
        outer apply Doc.Z.nodes('myroot/Test') as D(Z)
    

    sql fiddle demo