What's New in FOR XML in Microsoft SQL Server 2005
Michael Rys
Microsoft Corporation
June 2004
updated June 2005
Applies to:
Microsoft SQL Server 2005
Summary: This first in a series of articles by Michael Rys details the major new capabilities of the server-side FOR XML clause in the next version of SQL Server. These enable you to further enhance the XML support in your applications and to write easy-to-maintain relational data-to-XML aggregations. (12 printed pages)
Contents
Introduction
Backwards-Compatibility with SQL Server 2000
Integration with the XML Data Type
Assigning FOR XML Results
Nesting of FOR XML Expressions
The New PATH Mode
Adding XML Namespaces
Recursion and FOR XML
More FOR XML Extensions
Conclusion
Introduction
In Microsoft SQL Server 2000, we introduced the FOR XML clause to the SELECT statement. This clause provides the ability to aggregate the relational rowset returned by the SELECT statement into XML. FOR XML on the server supports three modes—RAW, AUTO, and EXPLICIT—that provide different transformation semantics.
RAW mode generates single elements, which are named row, for each row returned.
The AUTO mode employs a heuristic to infer a simple, one element name-per-level hierarchy based on the lineage information and the order of the data in a SELECT statement.
Finally, the EXPLICIT mode requires a specific rowset format that can be mapped into almost any XML shape, while still being formulated by a single SQL query.
All three modes are designed to generate the XML in a streamable way in order to be able to produce large documents efficiently.
The EXPLICIT mode format is highly successful in achieving its goals. There are only a few things it cannot do (arbitrary recursive part-list trees are one of them). The preferred SQL expression to generate the rowset format is a formidable "query from hell," however.
Unfortunately, FOR XML results in SQL Server 2000 can only be consumed on the client side, and authoring complex XML structures using FOR XML EXPLICIT is a complex exercise. As one of the designers of the rowset format, I have all the respect for people who write, and then maintain, multi-thousand line EXPLICIT mode queries. I also, however, understand the usability, maintainability, and complexity issues.
In SQL Server 2005, we now have alternatives to the "query from hell." In the following, we will take a look at the main features added to FOR XML in SQL Server 2005: Integration with the XML data type, assignability and nesting of expressions, and the new PATH mode. We provide some Northwind schema-based examples that show how EXPLICIT mode queries can be more easily written using the new features.
Besides these new features, FOR XML also provides a couple of additional new, smaller features that are summarized at the end of this section and supports an easier way to generate namespace-aware documents.
Note FOR XML continues to be a rowset aggregation clause of the SQL SELECT statement, and thus cannot transform side-effect output from stored procedures. If you want results of some form of processing transformed into XML, please use either a user-defined function or a view.
Backwards-Compatibility with SQL Server 2000
An important aspect of FOR XML in SQL Server 2005 is the backwards-compatibility with FOR XML in SQL Server 2000. The backwards-compatibility aspects can be split into the following categories: bug fixes, compatibility mode changes, and preserving FOR XML behavior.
In the first category, bug fixes, we incorporated some bug fixes with respect of treatment of views and subqueries in the AUTO mode. We also changed the entitization rules in order to preserve whitespaces, and to minimize entitization (this change has no semantic impact). Since these are considered bug fixes, they occur regardless of the server compatibility flag.
Many users felt that instances of the SQL type timestamp should have been reported as a (base64-encoded) binary value instead of the number representation. We therefore changed the treatment of timestamp values in SQL Server 2005 to be serialized as base64 encoded binary values. The SQL Server 2000 behavior can still be requested by setting the server's compatibility level to SQL Server 2000.
Finally, for performance reasons SQL Server 2000 returned results in a streaming way, without checking for well-formedness. This means that the result could contain invalid XML characters such as U+0007 (the ASCII BELL character), or (by means of a corrupted !xmltext field) have non-wellformed structure. All such cases are normally detected by the client-side parsers, but a user may not use an XML parser to consume the data, instead using a simple substring extraction. It was therefore decided to preserve this behavior in a backwards-compatible way in SQL Server 2005 regardless of compatibility level. Since the new XML data type does not accept such invalid XML (although it can deal with the fragment result form of FOR XML), a normal FOR XML query in SQL Server 2005 will return its result as an instance of type nvarchar(max). More precisely, it will return its result as a single row, single column rowset where the cell contains the string value of type nvarchar(max).
Integration with the XML Data Type
With the introduction of the XML data type, we wanted to also give FOR XML the ability to generate an instance of XML directly (more precisely, it generates a single row, single column rowset where the cell contains the XML data type instance).
Because of the backwards-compatibility considerations outlined above, we added a new TYPE directive to generate the result as XML. For example,
SELECT * FROM Customers FOR XML AUTO, TYPE
returns the Customers elements as an XML data type instance, instead of the nvarchar(max) instance that would have been the case without the TYPE directive.
This result is guaranteed to conform to the well-formedness constraints provided by the XML data type. Since the result is an XML data type instance, you can also use XQuery expressions to query and reshape the result. For example, the following expression retrieves the Customer contact name into a new Person element.
SELECT (SELECT * FROM Customers FOR XML AUTO, TYPE).query(
'<doc>{
for $c in /Customers
return
<Person name="{data($c/@ContactName)}"/>
}</doc>')
returns (only first elements shown),
<doc>
<Person name="Maria Anders" />
<Person name="Ana Trujillo" />
<Person name="Antonio Moreno" />
...
</doc>
Assigning FOR XML Results
Since FOR XML queries now return assignable values, the result of a FOR XML query can be assigned to a variable, or inserted into a column.
DECLARE @cust XML;
SET @cust = (SELECT * FROM Customers FOR XML AUTO, TYPE)
CREATE TABLE T(i int, x XML)
INSERT INTO T SELECT 1, (SELECT * FROM Customers FOR XML AUTO, TYPE)
Nesting of FOR XML Expressions
FOR XML, in SQL Server 2005, recognizes XML data type columns, and will inline them as sub-elements. Thus, we can nest FOR XML queries to generate hierarchies, instead of having to rely on the AUTO mode heuristic, or writing an EXPLICIT mode query.
Let's look at the example. The following FOR XML EXPLICIT query is returning Customer elements, containing their orders and the employees that work on their orders. For simplification, we are only returning a single property per element.
SELECT 1 as TAG,
NULL as Parent,
CustomerID as "Customer!1!CustomerID",
NULL as "Order!2!OrderID",
NULL as "Employee!3!LastName"
FROM Customers
UNION ALL
SELECT 2,
1,
Customers.CustomerID,
Orders.OrderID,
NULL
FROM Orders
JOIN Customers ON Orders.CustomerID = Customers.CustomerID
UNION ALL
SELECT DISTINCT 3,
1,
Customers.CustomerID,
NULL,
Employees.LastName
FROM Customers
JOIN Orders ON Customers.CustomerID = Orders.CustomerID
JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID
ORDER BY "Customer!1!CustomerID","Employee!3!LastName","Order!2!OrderID"
FOR XML EXPLICIT
returns (only first customer shown),
<Customer CustomerID="ALFKI">
<Order OrderID="10643" />
<Order OrderID="10692" />
<Order OrderID="10702" />
<Order OrderID="10835" />
<Order OrderID="10952" />
<Order OrderID="11011" />
<Employee LastName="Davolio" />
<Employee LastName="Leverling" />
<Employee LastName="Peacock" />
<Employee LastName="Suyama" />
</Customer>
...
As you can see, we need a select statement for each element. We also repeat the parent's identification with every child, so that the order by groups the children with their parents. The streaming serialization from the rowset to XML depends on this grouping to get the correct nesting.
Now let's look at how we can rewrite this by using nesting FOR XML expressions. We can leverage the new TYPE directive to generate XML data type instances (otherwise, you will get a textual result that will be entitized if it is embedded in another FOR XML query) and nest sub selections to define the hierarchy.
We use a separate FOR XML query for each of the three "entity" elements, and nest them to express the hierarchy. We now rewrite the previous EXPLICIT mode query using the AUTO mode and nesting:
SELECT CustomerID as "CustomerID",
(SELECT OrderID as "OrderID"
FROM Orders "Order"
WHERE "Order".CustomerID = Customer.CustomerID
FOR XML AUTO, TYPE),
(SELECT DISTINCT LastName as "LastName"
FROM Employees Employee
JOIN Orders "Order" ON "Order".EmployeeID = Employee.EmployeeID
WHERE Customer.CustomerID = "Order".CustomerID
FOR XML AUTO, TYPE)
FROM Customers Customer
FOR XML AUTO, TYPE
This returns, except for guaranteeing the element order, the same result as the EXPLICIT mode query (if you care about the order, you can add an order by statement).
Obviously, this query is easier to write, understand, and maintain, even though it contains basically the same number of select statements and join conditions.
The New PATH Mode
In the above section, we looked at how we can utilize some of the FOR XML capabilities to rewrite a simple FOR XML EXPLICIT query into a simpler query that exploits the nestability of FOR XML, the AUTO mode, and the new XML data type.
One may say, however, that the previous query was too simple to reflect a real world scenario with FOR XML EXPLICIT.
For example, some of the strengths of the explicit mode are to mix attributes and elements at will, create wrappers and nested, complex properties, and even create space-separated value lists and mixed content. None of these results can be achieved by nesting FOR XML AUTO queries. So do we still have to write EXPLICIT mode queries to get these mapping capabilities?
Don't despair! A new FOR XML mode gives you the same flexibility in a much simpler way. The new PATH mode, together with the ability to nest FOR XML expressions, has become the simplest way to generate complex XML documents.
The PATH mode allows you to use an XPath-like syntax as a column name, which then is mapped into an attribute (e.g., "@a"), element (e.g., "e"), sub element structure ("e1/e2"), element content ("*"), text node ("text()"), or data value ("data()"). As with the RAW mode, the default name for the row element is row and can be overwritten with an NCName (a name without a prefix).
Let's look at some examples. First, let us give the PATH mode formulation of the above EXPLICIT mode query.
SELECT CustomerID as "@CustomerID",
(SELECT OrderID as "@OrderID"
FROM Orders
WHERE Orders.CustomerID = Customers.CustomerID
FOR XML PATH('Order'), TYPE),
(SELECT DISTINCT LastName as "@LastName"
FROM Employees
JOIN Orders ON Orders.EmployeeID = Employees.EmployeeID
WHERE Customers.CustomerID = Orders.CustomerID
FOR XML PATH('Employee'), TYPE)
FROM Customers
FOR XML PATH('Customer')
This example is similar to the AUTO mode version and returns the same result.
Now let's look at some of the PATH mode specific capabilities. The next query takes the customer information and groups the address and contact information into separate sub elements using a more complex path expression as column alias, and—using the new ROOT directive—adds a root node around it for good measure.
SELECT CustomerID as "@CustomerID",
CompanyName,
Address as "address/street",
City as "address/city",
Region as "address/region",
PostalCode as "address/zip",
Country as "address/country",
ContactName as "contact/name",
ContactTitle as "contact/title",
Phone as "contact/phone",
Fax as "contact/fax"
FROM Customers
FOR XML PATH('Customer'), ROOT('doc')
This query results in the following document (only showing the first customer element).
<doc>
<Customer CustomerID="ALFKI">
<CompanyName>Alfreds Futterkiste</CompanyName>
<address>
<street>Obere Str. 57</street>
<city>Berlin</city>
<zip>12209</zip>
<country>Germany</country>
</address>
<contact>
<name>Maria Anders</name>
<title>Sales Representative</title>
<phone>030-0074321</phone>
<fax>030-0076545</fax>
</contact>
</Customer>
...
</doc>
How would this query look like if it used the EXPLICIT mode? We need four select clauses—one for each non-leaf element—instead of only one selection.
SELECT top 1
1 as TAG,
NULL as Parent,
1 as "doc!1!dummy!hide",
NULL as "Customer!2!CustomerID",
NULL as "Customer!2!CompanyName!element",
NULL as "address!3!street!element",
NULL as "address!3!city!element",
NULL as "address!3!region!element",
NULL as "address!3!zip!element",
NULL as "address!3!country!element",
NULL as "contact!4!name!element",
NULL as "contact!4!title!element",
NULL as "contact!4!phone!element",
NULL as "contact!4!fax!element"
FROM Customers
UNION ALL
SELECT 2, 1,
1,
CustomerID, CompanyName,
NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL
FROM Customers
UNION ALL
SELECT 3, 2,
1,
CustomerID, NULL,
Address, City, Region, PostalCode, Country,
NULL, NULL, NULL, NULL
FROM Customers
UNION ALL
SELECT 4, 2,
1,
CustomerID, NULL,
NULL, NULL, NULL, NULL, NULL,
ContactName, ContactTitle, Phone, Fax
FROM Customers
ORDER BY "doc!1!dummy!hide","Customer!2!CustomerID"
FOR XML EXPLICIT, TYPE
Now we know why the EXPLICIT mode is sometimes called the "query from hell."
Last but not least, the following gives us an example of generating a value list, and shows the use of a text node.
SELECT CustomerID as "@ID",
(SELECT OrderID as "data()"
FROM Orders
WHERE Customers.CustomerID=Orders.CustomerID
FOR XML PATH('')
) as "@OrderIDs",
CompanyName,
ContactTitle as "ContactName/@ContactTitle",
ContactName as "ContactName/text()",
PostalCode as "Address/@ZIP",
Address as "Address/Street",
City as "Address/City"
FROM Customers
FOR XML PATH('Customer')
This creates a result of the form (showing one example customer),
<Customer ID="HUNGC" OrderIDs="10375 10394 10415 10600 10660">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName
ContactTitle="Sales Representative">Yoshi Latimer</ContactName>
<Address ZIP="97827">
<Street>City Center Plaza 516 Main St.</Street>
<City>Elgin</City>
</Address>
</Customer>
Let's dissect the relevant parts of the query.
The sub query, which generates the OrderIDs attribute list, maps the OrderID column values as atomic values (using the path data()). These values will then be serialized as a text node by adding a space between sibling atomic values, which are provided in the next cell in the rowset. We then avoid generating a name for the row by using the zero-length string as the PATH mode argument, so that we get a single string (note, there is no TYPE directive!) as the result of the FOR XML PATH expression. That string gets mapped to the OrderIDs attribute by the containing FOR XML expression.
The CompanyName gets mapped to a sub element of the same name.
The ContactTitle produces the ContactTitle attribute of the ContactName element, while the ContactName column value is mapped into the text node of the same element. Note that in this case, the same result would have been achieved by mapping ContactName to the ContactName element directly.
Finally, the properties for the Address element parts are put together.
Adding XML Namespaces
XML Namespaces are becoming an increasingly important aspect of authoring XML documents for information interchange. They are used to disambiguate different vocabularies, identify ownership of a vocabulary, and to associate XML Schema information (and potentially other information) with an XML element or attribute.
FOR XML, in SQL Server 2000, puts the burden of generating and maintaining XML namespaces completely on the query writer. The XML namespace declaration attributes had to be created, like every other attribute, with the namespace URI being the column value. Unless the generated XML was in attribute-centric form, this meant that the query had to be written using the EXPLICIT mode. For example, the following query puts the resulting Customer elements and its property elements into the namespace urn:example.com/customer:
SELECT 1 as tag, NULL as parent,
'urn:example.com/customer' as "cust:Customer!1!xmlns:cust",
CustomerID as "cust:Customer!1!cust:CustomerID!element",
ContactName as "cust:Customer!1!cust:ContactName!element"
FROM Customers
FOR XML EXPLICIT
The result of the query is (only first two elements shown):
<cust:Customer xmlns:cust="urn:example.com/customer">
<cust:CustomerID>ALFKI</cust:CustomerID>
<cust:ContactName>Maria Anders</cust:ContactName>
</cust:Customer>
<cust:Customer xmlns:cust="urn:example.com/customer">
<cust:CustomerID>ANATR</cust:CustomerID>
<cust:ContactName>Ana Trujillo</cust:ContactName>
</cust:Customer>
Namespace declarations are not really attributes in the XML data model. Thus, the PATH mode does not allow them to be specified as attributes.
In order to simplify the use of XML namespaces in FOR XML, we have added support for the WITH XMLNAMESPACES clause starting in the April CTP version of SQL Server 2005. The WITH XMLNAMESPACES clause is defined in the SQL:2003 standard as an extension to the general WITH clause that is commonly used to define common table expressions. The WITH clause can be placed on top-level SQL statements, such as SELECT, INSERT and UPDATE statements, and used inside a CREATE VIEW statement. The WITH XMLNAMESPACES clause can be used with the RAW, AUTO and PATH mode, but not with either the XMLSCHEMA and XMLDATA directives or the EXPLICIT mode.
The previous method of creating namespaces is still supported for the SQL Server 2000 modes, but cannot be mixed with the WITH XMLNAMESPACES clause. To disambiguate the WITH clause from other syntactic uses of WITH, T-SQL statements that precede WITH clauses need to be terminated with a semi-colon (;). The following query places the customer and order data into different namespaces and adds a root node in a default namespace.
WITH XMLNAMESPACES (
DEFAULT 'urn:example.com/doc'
, 'urn:example.com/customer' as "c"
, 'urn:example.com/order' as "o"
)
SELECT CustomerID as "@ID",
(SELECT OrderID as "@OrderID"
from Orders
where Customers.CustomerID=Orders.CustomerID
FOR XML PATH('o:Order'), TYPE
) as "c:Orders",
CompanyName as "c:CompanyName",
ContactTitle as "c:ContactName/@ContactTitle",
ContactName as "c:ContactName/text()",
PostalCode as "c:Address/@ZIP",
Address as "c:Address/c:Street",
City as "c:Address/c:City"
FROM Customers
FOR XML PATH('c:Customer'), ROOT('doc')
As the following partial result shows, the XML namespace declarations are currently added at the top-level elements of every FOR XML selection:
<doc xmlns:o="urn:example.com/order" xmlns:c="urn:example.com/customer"
xmlns="urn:example.com/doc">
<c:Customer ID="ALFKI">
<c:Orders>
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="10643" />
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="10692" />
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="10702" />
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="10835" />
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="10952" />
<o:Order xmlns:o="urn:example.com/order"
xmlns:c="urn:example.com/customer" xmlns="urn:example.com/doc"
OrderID="11011" />
</c:Orders>
<c:CompanyName>Alfreds Futterkiste</c:CompanyName>
<c:ContactName
ContactTitle="Sales Representative">Maria Anders</c:ContactName>
<c:Address ZIP="12209">
<c:Street>Obere Str. 57</c:Street>
<c:City>Berlin</c:City>
</c:Address>
</c:Customer>
...
The above query used the DEFAULT clause for adding a default namespace. Note that if there are nested XML documents with no default namespace included in the result, a slight performance penalty has to be paid to make sure that they preserve their absence of a default namespace.
Finally, the WITH XMLNAMESPACES clause can also be used to provide namespace bindings for the XQuery and XML DML methods on the XML data type.
Recursion and FOR XML
One of the strengths of the XML format is that it can easily represent hierarchies, including structurally recursive hierarchies such as parts lists. In SQL Server 2000, you could not generate such structures without knowing the maximum depth at query formulation time. Since we now can nest FOR XML expressions, we can easily generate the recursive hierarchies using user-defined functions.
For example, the following user-defined function creates a nested XML document detailing the parts list for a specific part. First, let us define some example data:
CREATE TABLE PARTS(id int, parent int, name nvarchar(500))
GO
INSERT INTO PARTS
SELECT 1, NULL, N'car'
UNION
SELECT 2, 1, N'engine'
UNION
SELECT 3, 1, N'body'
UNION
SELECT 4, 3, N'door'
UNION
SELECT 5, 3, N'fender'
UNION
SELECT 6, 4, N'window'
UNION
SELECT 7, 2, N'piston'
Next, we define the function that for a given part number returns the subparts in XML form:
CREATE FUNCTION PartsList(@PartsNo int)
RETURNS XML
WITH RETURNS NULL ON NULL INPUT
BEGIN RETURN
(SELECT id as "@id", name as "@name",
CASE WHEN parent=@PartsNo
THEN dbo.PartsList(id)
END
FROM dbo.PARTS WHERE parent=@PartsNo
FOR XML PATH('Parts'), TYPE)
END
We use a CASE statement to make sure that the query execution does not calculate the function recursively if the optimizer decides to apply the filter after executing the selection. Executing the following expression will return part 3 with it subparts.
select id as "@id", name as "@name",
CASE WHEN id=3
THEN dbo.PartsList(id)
END
FROM PARTS
WHERE id=3
FOR XML PATH('Parts'), TYPE
Returns:
<Parts id="3" name="body">
<Parts id="4" name="door">
<Parts id="6" name="window" />
</Parts>
<Parts id="5" name="fender" />
</Parts>
Note that SQL Server 2005 has a maximum limit of 32 recursively nested function invocations. If your parts hierarchy exceeds the limit, you will need to use the old approach of getting the XML in flat form and applying an XSLT style sheet to create the hierarchy.
More FOR XML Extensions
Besides the new features listed above, SQL Server 2005 provides the following new features:
RAW mode can be combined with the ELEMENTS directive, and take a parameter to overwrite the row element name. For example,
SELECT * FROM Customers FOR XML RAW('Customer'), ELEMENTS
returns (only showing the first customer),
<Customer> <CustomerID>ALFKI</CustomerID> <CompanyName>Alfreds Futterkiste</CompanyName> <ContactName>Maria Anders</ContactName> <ContactTitle>Sales Representative</ContactTitle> <Address>Obere Str. 57</Address> <City>Berlin</City> <PostalCode>12209</PostalCode> <Country>Germany</Country> <Phone>030-0074321</Phone> <Fax>030-0076545</Fax> </Customer>
The ELEMENTS directive provides an XSINIL option to map NULL values to an element with an attribute xsi:nil="true". For example,
SELECT * FROM Customers WHERE Region is null FOR XML PATH('Customer'), ELEMENTS XSINIL
returns (only showing the first customer),
<Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <CustomerID>ALFKI</CustomerID> <CompanyName>Alfreds Futterkiste</CompanyName> <ContactName>Maria Anders</ContactName> <ContactTitle>Sales Representative</ContactTitle> <Address>Obere Str. 57</Address> <City>Berlin</City> <Region xsi:nil="true" /> <PostalCode>12209</PostalCode> <Country>Germany</Country> <Phone>030-0074321</Phone> <Fax>030-0076545</Fax> </Customer>
A new in-line schema inference directive XMLSCHEMA that takes a target namespace URI as an optional argument has been added for the RAW and AUTO modes. For example,
SELECT * FROM Customers FOR XML RAW('Customer'), XMLSCHEMA('urn:example.com')
returns (only showing parts of the schema and data),
<xsd:schema targetNamespace="urn:example.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes= "https://schemas.microsoft.com/sqlserver/2004/sqltypes" elementFormDefault="qualified"> <xsd:import namespace= "https://schemas.microsoft.com/sqlserver/2004/sqltypes" /> <xsd:element name="Customer"> ... </xsd:element> </xsd:schema> <Customer xmlns="urn:example.com" CustomerID="ALFKI" CompanyName="Alfreds Futterkiste" ContactName="Maria Anders" ContactTitle="Sales Representative" Address="Obere Str. 57" City="Berlin" PostalCode="12209" Country="Germany" Phone="030-0074321" Fax="030-0076545" /> ...
Note that the EXPLICIT and PATH modes – unlike the RAW and AUTO modes – are normally used when generating the XML according to a previously provided schema. Therefore we do not provide the schema inference directive with the EXPLICIT and PATH modes.
Conclusion
We provided a first look at the extended FOR XML support in SQL Server 2005. The added functionality, mostly enabled by the new XML data type, will make FOR XML a very powerful and easy to use tool to generate XML from your relational data. The new PATH mode, together with the nesting of FOR XML queries and the WITH XMLNAMESPACES clause, gives enough power to replace most of the EXPLICIT mode queries in a simpler, more maintainable way. The ability to nest FOR XML queries also provides a mechanism to generate recursive hierarchies.
There will still be a few cases where you may want to use the EXPLICIT mode (in order to generate CDATA sections or to use the !xmltext directive, for example), but the new functionality should make the "query from hell" a much less frequent encounter.