As of SQL Server 2005, xml is an intrinsic data type of SQL Server. Thus, you can cast the XML output from a FOR XML query directly into an xml
data type instance, as opposed to streaming XML results directly or
immediately to the client. You accomplish this by using the TYPE keyword after your FOR XML statement, as shown in Example 1.
Example 1. Using the TYPE option with FOR XML AUTO to cast a subquery result set as an xml data type.
SELECT
CustomerID,
(SELECT SalesOrderID, TotalDue, OrderDate, ShipDate
FROM Sales.SalesOrderHeader AS OrderHeader
WHERE CustomerID = Customer.CustomerID
FOR XML AUTO, TYPE) AS OrderHeaders
FROM
Sales.Customer AS Customer
WHERE
CustomerID IN (11000, 11001)
This query returns two columns. The first is the integer CustomerID and the second is an OrderHeaders column of type xml. The second column is constructed by a subquery that generates XML using FOR XML AUTO, and the TYPE option casts the generated XML from the subquery into an xml data type that gets returned as the OrderHeaders column of the main query.
As we already mentioned, FOR XML PATH gives you fine control over the generated XML much like FOR XML EXPLICIT does, but is much simpler to use. With FOR XML PATH, you simply assign column aliases with XPath expressions that shape your XML output, as shown in Example 2.
Example 2. Using FOR XML PATH to shape XML output with XPath-based column aliases.
SELECT
BusinessEntityID AS [@BusinessEntityID],
FirstName AS [ContactName/First],
LastName AS [ContactName/Last],
EmailAddress AS [ContactEmailAddress/EmailAddress1]
FROM
HumanResources.vEmployee
FOR XML PATH
('Contact')
The output looks like this:
<row BusinessEntityID="263">
<ContactName>
<First>Jean</First>
<Last>Trenary</Last>
</ContactName>
<ContactEmailAddress>
<EmailAddress1>[email protected]</EmailAddress1>
</ContactEmailAddress>
</row>
<row BusinessEntityID="78">
<ContactName>
<First>Reuben</First>
<Last>D'sa</Last>
</ContactName>
<ContactEmailAddress>
<EmailAddress1>[email protected]</EmailAddress1>
</ContactEmailAddress>
</row>
:
Notice that the BusinessEntityID column is rendered as an attribute. This is because it was aliased as @BusinessEntityID, and the @-symbol in XPath means “attribute.” Also notice that the FirstName and LastName columns are rendered as First and Last elements nested within a ContactName element. This again is due to the XPath-based syntax of the column aliases, ContactName/First and ContactName/Last.
Using the TYPE option in conjunction with FOR XML PATH, you can reproduce that awful and complex query with a much simpler version, as shown in Example 3.
Example 3. Using FOR XML PATH to shape XML output for a three-level hierarchy.
SELECT
CustomerID AS [@CustomerID],
(SELECT
SalesOrderID AS [@SalesOrderID],
TotalDue AS [@TotalDue],
OrderDate,
ShipDate,
(SELECT
ProductID AS [@ProductID],
OrderQty AS [@OrderQty],
LineTotal AS [@LineTotal]
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = OrderHeader.SalesOrderID
FOR XML PATH('OrderDetail'), TYPE)
FROM Sales.SalesOrderHeader AS OrderHeader
WHERE CustomerID = Customer.CustomerID
FOR XML PATH('OrderHeader'), TYPE)
FROM Sales.Customer AS Customer
INNER JOIN Person.Person AS Contact
ON Contact.BusinessEntityID = Customer.PersonID
WHERE CustomerID BETWEEN 11000 AND 11999
FOR XML PATH ('Customer')
In this simpler version that produces the same result, subqueries are used with the XML PATH statement in conjunction with TYPE to produce element-based XML nested inside a much larger FOR XML PATH statement. This returns each separate Order for the customer as a new child node of the CustomerID node. And again, XPath
syntax is used in the column aliases to define element and attribute
structure in the generated XML. Here are the results of the query:
<Customer CustomerID="11480">
<OrderHeader SalesOrderID="51053" TotalDue="2288.9187">
<OrderDate>2007-06-28T00:00:00</OrderDate>
<ShipDate>2007-07-05T00:00:00</ShipDate>
<OrderDetail ProductID="779" OrderQty="1" LineTotal="2071.419600" />
</OrderHeader>
<OrderHeader SalesOrderID="52329" TotalDue="2552.5169">
<OrderDate>2007-08-10T00:00:00</OrderDate>
<ShipDate>2007-08-17T00:00:00</ShipDate>
<OrderDetail ProductID="782" OrderQty="1" LineTotal="2294.990000" />
<OrderDetail ProductID="870" OrderQty="1" LineTotal="4.990000" />
<OrderDetail ProductID="871" OrderQty="1" LineTotal="9.990000" />
</OrderHeader>
<OrderHeader SalesOrderID="62813" TotalDue="612.1369">
<OrderDate>2008-01-26T00:00:00</OrderDate>
<ShipDate>2008-02-02T00:00:00</ShipDate>
<OrderDetail ProductID="999" OrderQty="1" LineTotal="539.990000" />
<OrderDetail ProductID="872" OrderQty="1" LineTotal="8.990000" />
<OrderDetail ProductID="870" OrderQty="1" LineTotal="4.990000" />
</OrderHeader>
</Customer>
<Customer CustomerID="11197">
<OrderHeader SalesOrderID="57340" TotalDue="46.7194">
:
If you are familiar and comfortable with XPath, you will appreciate some additional XML PATH features. You can use the following XPath node functions to further control the shape of your XML output:
data
comment
node
text
processing-instruction
The following example uses the data and comment methods of XPath. The data method takes the results of the underlying query and places them all inside one element. The comment method takes data and transforms it into an XML comment, as demonstrated in Example 4.
Example 4. Using FOR XML PATH with the comment and data XPath methods.
SELECT
Customer.BusinessEntityID AS [@CustomerID],
Customer.FirstName + ' ' + Customer.LastName AS [comment()]
,
(SELECT
SalesOrderID AS [@SalesOrderID],
TotalDue AS [@TotalDue],
OrderDate,
ShipDate,
(SELECT ProductID AS [data()]
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = OrderHeader.SalesOrderID
FOR XML PATH('')) AS [ProductIDs]
FROM Sales.SalesOrderHeader AS OrderHeader
WHERE CustomerID = Customer.BusinessEntityID
FOR XML PATH('OrderHeader'), TYPE)
FROM Sales.vIndividualCustomer AS Customer
WHERE BusinessEntityID IN (11000, 11001)
FOR XML PATH ('Customer')
As you can see from the results, the concatenated contact name becomes an XML comment, and the subquery of Product IDs is transformed into one element:
<Customer CustomerID="11000">
<!--Mary Young-->
<OrderHeader SalesOrderID="43793" TotalDue="3756.9890">
<OrderDate>2005-07-22T00:00:00</OrderDate>
<ShipDate>2005-07-29T00:00:00</ShipDate>
<ProductIDs>771</ProductIDs>
</OrderHeader>
<OrderHeader SalesOrderID="51522" TotalDue="2587.8769">
<OrderDate>2007-07-22T00:00:00</OrderDate>
<ShipDate>2007-07-29T00:00:00</ShipDate>
<ProductIDs>779 878</ProductIDs>
</OrderHeader>
<OrderHeader SalesOrderID="57418" TotalDue="2770.2682">
<OrderDate>2007-11-04T00:00:00</OrderDate>
<ShipDate>2007-11-11T00:00:00</ShipDate>
<ProductIDs>966 934 923 707 881</ProductIDs>
</OrderHeader>
</Customer>
<Customer CustomerID="11001">
<!--Amber Young-->
<OrderHeader SalesOrderID="43767" TotalDue="3729.3640">
<OrderDate>2005-07-18T00:00:00</OrderDate>
: