SQL Server FOR XML Explained

In SQL Server, the FOR XML clause allows us to return the results of a query as an XML document.

When we use this clause, we have various options for how we want to shape the resulting XML document.

Below is an explanation of how the FOR XML clause works, including the basic options we have when using it.

Modes

When using FOR XML, we specify which mode we want to use. The available modes are RAW, AUTO, PATH, and EXPLICIT. These are explained in the following table.

RAWGenerates a single <row> element per row in the rowset that is returned by the SELECT statement. We can also modify our query to create an XML hierarchy.
AUTOGenerates nesting in the resulting XML by using heuristics, based on the way the SELECT statement is specified. We have minimal control over the shape of the XML generated.
PATHProvides a simple way to shape the XML to our specific needs. Enables us to do many things that EXPLICIT mode can do, but with less complex code.
EXPLICITProvides the most flexibility in generating the XML we want from the query result. However, it requires a lot more complexity in our code than PATH (and the other modes).

Sample Query Without the FOR XML Clause

First, let’s look at a query without the FOR XML clause:

SELECT
    Owners.FirstName AS "OwnerFirstName",
    Owners.LastName AS "OwnerLastName",
    Pets.PetId,
    Pets.PetName,
    Pets.DOB,
    PetTypes.PetType
FROM Pets
    INNER JOIN PetTypes ON Pets.PetTypeId = PetTypes.PetTypeId
    INNER JOIN Owners ON Pets.OwnerId = Owners.OwnerId
ORDER BY Owners.OwnerId;

Result:

OwnerFirstName  OwnerLastName  PetId  PetName  DOB         PetType
--------------  -------------  -----  -------  ----------  -------
Bart            Pitt           3      Scratch  2018-10-01  Cat    
Nancy           Simpson        1      Fluffy   2020-11-20  Cat    
Nancy           Simpson        2      Fetch    2019-08-16  Dog    

This is what the data looks like when it’s returned in tabular format. This is what we’re used to when we run queries in SQL Server (without using FOR XML).

AUTO Mode

Now let’s append FOR XML AUTO to the above query:

SELECT
    Owners.FirstName AS "OwnerFirstName",
    Owners.LastName AS "OwnerLastName",
    Pets.PetId,
    Pets.PetName,
    Pets.DOB,
    PetTypes.PetType
FROM Pets
    INNER JOIN PetTypes ON Pets.PetTypeId = PetTypes.PetTypeId
    INNER JOIN Owners ON Pets.OwnerId = Owners.OwnerId
ORDER BY Owners.OwnerId
FOR XML AUTO;

When I run that with my command line tool, the XML is returned as one long string:

<Owners OwnerFirstName="Bart" OwnerLastName="Pitt"><Pets PetId="3" PetName="Scratch" DOB="2018-10-01"><PetTypes PetType="Cat"/></Pets></Owners><Owners OwnerFirstName="Nancy" OwnerLastName="Simpson"><Pets PetId="1" PetName="Fluffy" DOB="2020-11-20"><PetTypes PetType="Cat"/></Pets><Pets PetId="2" PetName="Fetch" DOB="2019-08-16"><PetTypes PetType="Dog"/></Pets></Owners>

The same happens when I run it in my GUI (Azure Data Studio). The XML is returned in one column and one row. However, when running it in the GUI, clicking on the XML in the column opens a new tab with the XML formatted like this:

<Owners OwnerFirstName="Bart" OwnerLastName="Pitt">
	<Pets PetId="3" PetName="Scratch" DOB="2018-10-01">
		<PetTypes PetType="Cat" />
	</Pets>
</Owners>
<Owners OwnerFirstName="Nancy" OwnerLastName="Simpson">
	<Pets PetId="1" PetName="Fluffy" DOB="2020-11-20">
		<PetTypes PetType="Cat" />
	</Pets>
	<Pets PetId="2" PetName="Fetch" DOB="2019-08-16">
		<PetTypes PetType="Dog" />
	</Pets>
</Owners>

We can see that SQL Server has returned the result as XML. The XML has been generated automatically by SQL Server, based on AUTO mode.

See SQL Server FOR XML AUTO Examples for more examples of what we can do when using AUTO mode.

RAW Mode

Here’s an example of a query that uses RAW mode:

SELECT 
    p.PetId,
    p.PetName,
    p.DOB,
    pt.PetType,
    CONCAT(o.FirstName, ' ', o.LastName) AS OwnerName
FROM Pets p 
    INNER JOIN PetTypes pt ON p.PetTypeId = pt.PetTypeId
    INNER JOIN Owners o ON p.OwnerId = o.OwnerId
FOR XML RAW;

Result:

<row PetId="1" PetName="Fluffy" DOB="2020-11-20" PetType="Cat" OwnerName="Nancy Simpson" />
<row PetId="2" PetName="Fetch" DOB="2019-08-16" PetType="Dog" OwnerName="Nancy Simpson" />
<row PetId="3" PetName="Scratch" DOB="2018-10-01" PetType="Cat" OwnerName="Bart Pitt" />

When we use RAW mode, each row in the result is presented as a separate element called <row>. We can change the name of this element if required.

In RAW mode, the columns are presented as attributes to the <row> element (or whatever name we give it). We can change our query to have the columns presented in their own elements.

See SQL Server FOR XML RAW Examples for more examples that use this mode.

PATH Mode

Here’s an example of a query that uses PATH mode:

SELECT
    p.PetId,
    p.PetName,
    p.DOB,
    pt.PetType,
    CONCAT(o.FirstName, ' ', o.LastName) AS OwnerName
FROM Pets p 
    INNER JOIN PetTypes pt ON p.PetTypeId = pt.PetTypeId
    INNER JOIN Owners o ON p.OwnerId = o.OwnerId
FOR XML PATH;

Result:

<row>
	<PetId>
		1
	</PetId>
	<PetName>
		Fluffy
	</PetName>
	<DOB>
		2020-11-20
	</DOB>
	<PetType>
		Cat
	</PetType>
	<OwnerName>
		Nancy Simpson
	</OwnerName>
</row>
<row>
	<PetId>
		2
	</PetId>
	<PetName>
		Fetch
	</PetName>
	<DOB>
		2019-08-16
	</DOB>
	<PetType>
		Dog
	</PetType>
	<OwnerName>
		Nancy Simpson
	</OwnerName>
</row>
<row>
	<PetId>
		3
	</PetId>
	<PetName>
		Scratch
	</PetName>
	<DOB>
		2018-10-01
	</DOB>
	<PetType>
		Cat
	</PetType>
	<OwnerName>
		Bart Pitt
	</OwnerName>
</row>

As with RAW mode, we can change the name of the <row> element to one of our choosing. We can also remove it altogether, add a root element, and more.

By default, the columns are presented as elements in an XML hierarchy. But we can change this if required. We can specify that some, or all, columns are to be presented as attributes.

We can also create our own elements to produce a “parent/child” pattern that interacts with the columns from our query. In other words, we can nest columns from the query inside a newly created element of our choosing.

For more examples, see SQL Server FOR XML PATH Examples.

EXPLICIT Mode

Here’s an example of a query that uses EXPLICIT mode:

SELECT
    1 AS Tag,
    NULL AS Parent,
    p.PetId AS [Pet!1!PetId],
    NULL AS [Details!2!PetName],
    NULL AS [Details!2!PetType]
FROM Pets p 
    INNER JOIN PetTypes pt ON p.PetTypeId = pt.PetTypeId
UNION ALL
SELECT
    2 AS Tag,
    1 AS Parent,
    p.PetId,
    p.PetName,
    pt.PetType
FROM Pets p 
    INNER JOIN PetTypes pt ON p.PetTypeId = pt.PetTypeId
ORDER BY [Pet!1!PetId], [Details!2!PetName]
FOR XML EXPLICIT;

Result:

<Pet PetId="1">
	<Details PetName="Fluffy" PetType="Cat" />
</Pet>
<Pet PetId="2">
	<Details PetName="Fetch" PetType="Dog" />
</Pet>
<Pet PetId="3">
	<Details PetName="Scratch" PetType="Cat" />
</Pet>

As mentioned, EXPLICIT mode is a lot more involved than the other options. In most cases we can use PATH mode to achieve the same result, but EXPLICIT mode does allow us to be very specific with how we want our results to be returned.

See SQL Server FOR XML EXPLICIT Examples for more examples, as well as an explanation of how this mode works.

More Information

The FOR XML clause includes a few other options that allow us to customise our XML precisely to our needs. For more information see Microsoft’s documentation on the FOR XML clause and its basic syntax.