Question

I am using the following SELECT to fetch data from a database.

Is there a way to order the results by an item from the nested select? I tried ORDER BY B.ID but that creates an error.

My stored procedure:

SELECT      (
                SELECT      B.ID,
                            B.FirstName,
                            B.LastName
                FROM        Users B
                WHERE       B.UserNum = A.UserNum
                FOR XML     PATH(''), ELEMENTS, TYPE
            )
FROM        User_SolutionRole A
WHERE       A.Solution = 'SPR'
FOR XML PATH('users'), ELEMENTS, TYPE, ROOT('ranks')
Was it helpful?

Solution

If we could apply the values() method to the main SELECT's only column to extract the ID values from it, we could use the results to sort the rows, like this:

<column_ref>.values('ID[1]', 'int') ASC

It is actually possible to achieve that. If you assigned the column the name users and, at the same time, replaced PATH('users') with PATH(''), like this:

SELECT      (
                SELECT      B.ID,
                            B.FirstName,
                            B.LastName
                FROM        Users B
                WHERE       B.UserNum = A.UserNum
                FOR XML     PATH(''), ELEMENTS, TYPE
            ) AS users
FROM        User_SolutionRole A
WHERE       A.Solution = 'SPR'
FOR XML     PATH(''), ELEMENTS, TYPE, ROOT('ranks')
;

you would get the same output but the column would now have a reference. That would be your first step towards the goal.

It would seem you'd only need to add an ORDER BY like this:

SELECT      (
                SELECT      B.ID,
                            B.FirstName,
                            B.LastName
                FROM        Users B
                WHERE       B.UserNum = A.UserNum
                FOR XML     PATH(''), ELEMENTS, TYPE
            ) AS users
FROM        User_SolutionRole A
WHERE       A.Solution = 'SPR'
ORDER BY    users.value('ID[1]', 'int') ASC
FOR XML     PATH(''), ELEMENTS, TYPE, ROOT('ranks')
;

That wouldn't work, however, not at this point, because the alias assigned in a SELECT clause can only be referenced by the same level ORDER BY clause as is, i.e. it cannot be part of an expression. And so the above would result in a compilation error.

The solution to that would be to assign the name prior to the main SELECT. There are basically two ways:

  1. Using a derived table:

    SELECT      users
    FROM        (
                    SELECT      (
                                    SELECT      B.ID,
                                                B.FirstName,
                                                B.LastName
                                    FROM        Users B
                                    WHERE       B.UserNum = A.UserNum
                                    FOR XML     PATH(''), ELEMENTS, TYPE
                                ) AS users
                    FROM        User_SolutionRole A
                    WHERE       A.Solution = 'SPR'
                ) AS s
    ORDER BY    users.value('ID[1]', 'int') ASC
    FOR XML     PATH(''), ELEMENTS, TYPE, ROOT('ranks')
    ;

    The above uses a "normal" subselect but it would be no difference if you declared it as a CTE instead.

  2. Using CROSS APPLY:

    SELECT      x.users
    FROM        User_SolutionRole A
    CROSS APPLY (
                    SELECT      B.ID,
                                B.FirstName,
                                B.LastName
                    FROM        Users B
                    WHERE       B.UserNum = A.UserNum
                    FOR XML     PATH(''), ELEMENTS, TYPE
                ) AS x (users)
    WHERE       A.Solution = 'SPR'
    ORDER BY    x.users.value('ID[1]', 'int') ASC
    FOR XML     PATH(''), ELEMENTS, TYPE, ROOT('ranks')
    ;

My personal preference would be the CROSS APPLY technique as looking simpler and (to me) clearer, but the former method would work equally well.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top