Recommended resource
Listen to business books on the go.
Try Amazon audiobooks for commutes, workouts, and focused learning between meetings.
Affiliate link. If you buy through it, this site may earn a commission at no extra cost to you.
⏱ 18 min read
Stop fighting the XML beast. If you are trying to turn a set of rows into a comma-separated list or a nested hierarchy in SQL Server, Understanding FOR XML PATH in SQL Server: A No-Nonsense Guide is the only map you need. This feature is the engine room of SQL Server’s string manipulation capabilities. It allows you to construct custom XML, which is the primary way SQL Server serializes data into text formats like HTML tables, RSS feeds, or even JSON-like structures.
The biggest misconception is that FOR XML is just for XML. In the real world, developers often use it to generate comma-separated values (CSV), pipe-delimited lists, or custom formatting without writing complex nested loops in application code. It is fast, set-based, and surprisingly flexible once you stop treating it like a rigid formatting tool and start treating it as a string builder.
However, it is not magic. It does not understand JSON natively, and it does not care about your data types unless you tell it to. This guide cuts through the documentation noise to show you exactly how to wield it, where it breaks, and why you might want to avoid it entirely in favor of newer features.
The Mechanics of FOR XML PATH: How It Actually Works
At its core, FOR XML PATH is a clause that changes the behavior of a SELECT statement. Normally, a query returns a table of rows and columns. When you append FOR XML PATH('element_name'), SQL Server takes that result set and wraps every row in a tag named element_name. It then concatenates these tags into a single string.
The magic happens in the PATH argument. While you can just use FOR XML PATH('') to flatten everything, the real power lies in nested queries. You can run a subquery inside the main query, wrap that subquery with FOR XML PATH, and then wrap the result in another FOR XML PATH. This allows you to build arbitrarily deep hierarchies, mimicking the structure of an XML document or a JSON object.
Consider a simple table of employees. Without FOR XML, you get rows. With it, you get a string that looks like this:
<Employee><Name>Alice</Name><ID>1</ID></Employee><Employee><Name>Bob</Name><ID>2</ID></Employee>
Notice the lack of whitespace. SQL Server is ruthless about this. If you want newlines or indentation, you must explicitly add them as strings within your column aliases or concatenation logic. It does not guess. It does not care about readability unless you force it to.
The PATH argument acts as the root node. If you omit it or use an empty string, the root node is often omitted or becomes <ROOT>. This distinction is critical when parsing the output later. If your downstream application expects a specific root tag, you must specify it. If you leave it blank, you might end up with an unparseable string because the parser expects a root element.
Furthermore, FOR XML PATH processes rows sequentially. It does not know about groups unless you tell it using GROUP BY. This means if you want a list of distinct departments, you cannot just SELECT Department and expect one row per department. You must aggregate the data first or use DISTINCT carefully, understanding that the order of rows is determined by the underlying sort order of the database, which can be non-deterministic without an explicit ORDER BY clause.
The Hidden Trap of Whitespace
One of the most common pain points for developers is the absence of whitespace. When you generate XML, the output is a continuous stream of characters. If you try to render this in a browser or a text editor expecting pretty-printed XML, it will look like a mess of characters on a single line.
To fix this, you have to manually inject newlines and spaces. You can do this by concatenating string literals like ' or
'' ' into your column aliases. While this works, it is ugly and brittle. A better approach, which we will cover later, is to use FOR XML PATH combined with FOR XML RAW or EXPLICIT to handle structure, and then use string functions to format the final output.
Key Takeaway: SQL Server does not auto-format XML. Any indentation or newlines must be explicitly constructed using string concatenation within your query.
This lack of automation is why many developers hate FOR XML PATH. It feels like writing code in a language that doesn’t speak your dialect. You have to be the architect of the formatting, not just the architect of the data. This adds a layer of complexity that can be avoided with other tools, but it also gives you granular control that those tools often lack.
Building Flat Lists: The Comma-Separated Value Scenario
The most frequent use case for FOR XML PATH is generating a comma-separated list. Imagine you have a table of product categories and you want to list all category names for a specific product. You cannot simply SELECT CategoryName because that returns multiple rows, and your application code expects a single string like “Electronics, Clothing, Toys”.
Using FOR XML PATH, you can achieve this with a single query. The trick is to alias the column you want to concatenate as a string literal containing a comma and a space, then wrap it in FOR XML PATH(''). Finally, wrap that result in STUFF to remove the leading comma.
Here is a practical example. Let’s assume we have a Products table and a Categories table.
SELECT STUFF((
SELECT ', ' + c.Name
FROM Categories c
WHERE c.ProductID = p.ProductID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS CategoryList
FROM Products p
WHERE p.ProductID = 101;
In this query, the inner SELECT builds the list. ', ' + c.Name ensures every name is preceded by a comma and a space. FOR XML PATH('') serializes these rows into a string. The TYPE cast is crucial here. Without it, the result is of type XML, and you cannot easily pass it to STUFF or other string functions. Casting to NVARCHAR(MAX) converts the XML structure into a raw string.
The STUFF function then removes the first two characters (the leading comma and space) to clean up the output. This pattern is standard practice in SQL Server and is the primary reason why FOR XML PATH remains relevant despite the existence of JSON functions.
Why Not Just Use STRING_AGG? You Asked, We Answered
It is impossible to talk about FOR XML PATH in 2024 without mentioning STRING_AGG. Introduced in SQL Server 2017, STRING_AGG is the modern, clean alternative for flat lists. It does exactly what the FOR XML PATH trick does but with far less code.
STRING_AGG syntax is straightforward:
SELECT STRING_AGG(c.Name, ', ') AS CategoryList
FROM Categories c
WHERE c.ProductID = p.ProductID;
It is shorter, more readable, and less error-prone. So, why use FOR XML PATH? The answer lies in complexity and hierarchy. STRING_AGG is great for flat lists. It struggles when you need to nest lists inside lists. If you need a category list inside a product list inside a company list, STRING_AGG gets messy very quickly. FOR XML PATH, on the other hand, thrives on nesting.
You can nest FOR XML PATH queries to create multi-level hierarchies. You can wrap a STRING_AGG call inside a FOR XML PATH call to generate a JSON-like structure where arrays are strings within a larger XML structure. In those specific, complex scenarios, FOR XML PATH is still the only tool that fits the bill.
Furthermore, FOR XML PATH allows you to manipulate the structure of the output beyond simple concatenation. You can add attributes to the tags, conditional logic based on data values, and complex formatting. While STRING_AGG is a hammer for nails, FOR XML PATH is a Swiss Army knife for data serialization.
Caution: Always use
TYPEwhen casting the result ofFOR XML PATHto a string variable or function argument to avoid type mismatches in string manipulation functions.
This distinction is vital. If you are writing code for SQL Server 2017 or newer and your requirements are simple, reach for STRING_AGG. If you are dealing with older versions or complex hierarchical data, FOR XML PATH is the reliable veteran you need to trust.
Mastering Hierarchies: The Recursive Nightmare Solver
This is where FOR XML PATH shines brightest. Building hierarchical data structures in SQL Server is notoriously difficult. You need to represent parent-child relationships, which often requires self-joins and recursive common table expressions (CTEs). When you finally get the data, you want it in a structured format that your application can parse easily.
FOR XML PATH is designed for this. It allows you to build trees by nesting subqueries. The standard pattern involves a recursive CTE to gather the hierarchy, then a final SELECT that uses FOR XML PATH to format the result.
Consider an OrganizationalChart table with EmployeeID, ManagerID, and Name. You want to output a tree of employees, including their direct reports.
WITH EmployeeTree AS (
-- Anchor member: Get top-level employees
SELECT EmployeeID, Name, ManagerID, 0 AS Level
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
-- Recursive member: Find subordinates
SELECT e.EmployeeID, e.Name, e.ManagerID, et.Level + 1
FROM Employees e
INNER JOIN EmployeeTree et ON e.ManagerID = et.EmployeeID
)
SELECT
et.EmployeeID,
et.Name,
et.Level,
(SELECT '<Subordinates>' + STUFF((
SELECT ', <Employee>' + et2.Name
FROM EmployeeTree et2
WHERE et2.ManagerID = et.EmployeeID
ORDER BY et2.Name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + '</Subordinates>'
FOR XML PATH('Employee'), TYPE).value('.', 'NVARCHAR(MAX)') AS Hierarchy
FROM EmployeeTree et
ORDER BY et.Level, et.Name;
This query is dense, but the logic is clear. The CTE builds the flat list of all employees with their levels. The final SELECT uses a correlated subquery to look up subordinates for each employee. It then uses FOR XML PATH to wrap those subordinates in tags.
The result is a recursive string where each level is nested inside the previous one. This allows you to represent a full organizational tree in a single column. Your application can then parse this string to display the tree or save it for later use.
The Performance Cost of Deep Nesting
While this approach is elegant, it comes with a performance penalty. Every time you nest a FOR XML PATH query inside another, you are essentially running a query within a query within a query. This can lead to significant overhead, especially on large datasets.
The database engine has to evaluate the inner query for every row in the outer query. If you have 10,000 employees and an average of 10 subordinates, you are running the subordinate query 10,000 times. This is why deep hierarchies should be avoided in production environments with large data volumes.
A better approach for large datasets is to pre-compute the hierarchy into a temporary table or a view. You can store the hierarchy as a JSON string or a delimited path in the database. When you need to display it, you simply read the pre-computed value. This shifts the computational load from the reporting query to a background job that runs periodically.
Alternatively, you can use FOR XML EXPLICIT for very deep hierarchies. EXPLICIT gives you more control over the schema of the XML, allowing you to define paths explicitly rather than relying on the implicit behavior of PATH. However, EXPLICIT is notoriously difficult to write and debug. It is often better to stick with PATH unless you have a specific need for the explicit schema.
Practical Insight: For deep hierarchies in production, avoid real-time recursive XML generation. Pre-compute the hierarchy into a JSON column or a delimited path string to ensure query performance remains consistent.
This trade-off between flexibility and performance is a constant theme in SQL Server development. FOR XML PATH offers incredible flexibility, but it demands that you manage the cost of that flexibility. Understanding this balance is key to writing efficient queries.
Common Pitfalls and How to Avoid Them
Even experienced developers stumble over FOR XML PATH. It is easy to make mistakes that result in unparseable output or incorrect data. Here are the most common pitfalls and how to avoid them.
1. The Missing Root Tag
If you use FOR XML PATH(''), the root tag is often omitted. This can cause issues if your application expects a specific root element. Always specify a meaningful root tag, such as 'Root' or 'Data', to ensure the output is valid XML.
2. Type Mismatches
When concatenating strings within FOR XML PATH, ensure all columns are converted to strings. If you try to concatenate an integer with a string without casting, you might get unexpected results or errors depending on the SQL Server version. Always use CAST or CONVERT to ensure consistency.
3. Order of Rows
SQL Server does not guarantee the order of rows in a result set unless you specify an ORDER BY clause. If your hierarchy relies on a specific order (e.g., alphabetical by name), you must include ORDER BY in every subquery. Failure to do so can result in a confusing, unsorted tree.
4. Handling NULLs
If a column contains NULL, it will appear as the text “NULL” in the XML output. This is often not what you want. You should use ISNULL or COALESCE to replace NULL values with empty strings or default values before passing them to FOR XML PATH.
5. Escaping Special Characters
If your data contains special XML characters like <, >, &, or ", they will not be escaped automatically by FOR XML PATH. You must manually escape them or use the xml() data type to handle the escaping. Failure to do so can result in broken XML tags or security vulnerabilities.
| Pitfall | Symptom | Solution |
|---|---|---|
| Missing Root Tag | Output is just a list of tags without a container | Always specify a root tag in FOR XML PATH('Root') |
| Type Mismatch | Error when casting result to string | Use TYPE to cast the XML result to NVARCHAR(MAX) |
| Unordered Rows | Hierarchy appears random or jumbled | Add ORDER BY to every subquery within the XML generation |
| NULL Values | “NULL” appears in the output | Use ISNULL(column, '') to replace NULLs |
| Special Characters | Broken XML tags or syntax errors | Escape characters manually or use xml() data type |
These pitfalls are not insurmountable, but they require attention. By being aware of them, you can write more robust queries that produce reliable output. Always test your XML generation queries in a controlled environment before deploying them to production.
Beyond XML: Using FOR XML PATH for JSON-like Structures
While FOR XML PATH is designed for XML, it is frequently used to generate JSON-like structures. This is because the structure of XML (tags, attributes, nesting) is very similar to JSON. By carefully constructing the output, you can create a string that looks and behaves like JSON.
For example, you can generate an array of objects by using FOR XML PATH with specific aliases. The key is to use CONCAT or string concatenation to build the JSON syntax manually. This is not true JSON, as it lacks the proper escaping and validation, but it is often sufficient for internal data exchange or simple APIs.
Here is a simplified example of how to generate a JSON-like array:
SELECT
'[' +
STUFF((
SELECT ',' +
'{"id": ' + CAST(e.EmployeeID AS NVARCHAR) + ', "name": "' + REPLACE(e.Name, '"', '""') + '"}'
FROM Employees e
ORDER BY e.EmployeeID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') +
']' AS JSONLikeArray
This query generates a string that looks like a JSON array. It uses REPLACE to escape double quotes within the names, which is a requirement for valid JSON. Note that this is not a standard JSON function; it is a manual construction using FOR XML PATH.
Why Use This Approach?
You might ask, “Why not just use FOR JSON?” SQL Server 2016 introduced native JSON support, including FOR JSON PATH and FOR JSON AUTO. These features generate valid JSON without the need for manual string manipulation.
So, why use FOR XML PATH for JSON-like structures? The answer is compatibility and control. If you are using an older version of SQL Server, FOR JSON is not available. FOR XML PATH has been around for decades and is supported on all versions.
Additionally, FOR XML PATH gives you more control over the structure. You can add custom attributes, nested structures, and complex logic that might be difficult to achieve with FOR JSON. While FOR JSON is generally preferred for new development, FOR XML PATH remains a valuable tool for maintaining legacy systems or for specific use cases where custom formatting is required.
Recommendation: Use native
FOR JSONfor new projects to ensure valid JSON output. Only resort toFOR XML PATHfor legacy compatibility or highly custom formatting needs.
This hybrid approach allows you to leverage the strengths of both XML and JSON. By understanding how FOR XML PATH works, you can create structures that mimic JSON while maintaining the flexibility of XML.
When to Stop Using FOR XML PATH
Not every problem requires FOR XML PATH. In fact, using it in the wrong scenario can lead to unnecessary complexity and poor performance. There are cases where you should avoid it entirely.
1. Simple Flat Lists
If you need a simple comma-separated list, use STRING_AGG. It is cleaner, faster, and easier to read. There is no reason to use FOR XML PATH when STRING_AGG is available.
2. Valid JSON Requirements
If you need to generate valid JSON for an API or a client application, use FOR JSON. FOR XML PATH generated strings are not valid JSON and may cause issues with parsers that expect strict compliance.
3. Large Hierarchies
As mentioned earlier, deep hierarchies can be expensive. If you are dealing with thousands of rows in a hierarchy, consider pre-computing the structure or using a graph database for better performance.
4. Complex Data Types
If your data contains complex types like tables, arrays, or nested objects, FOR XML PATH might not handle them correctly. In these cases, consider using native JSON functions or a dedicated data serialization library.
Decision Matrix
| Scenario | Recommended Approach | Reason |
|---|---|---|
| Simple CSV/Comma List | STRING_AGG | Cleaner syntax, better performance |
| Valid JSON API Response | FOR JSON | Generates valid JSON, standard compliant |
| Deep Hierarchical Data | Pre-computed JSON/XML | Avoids recursive query overhead |
| Legacy System Support | FOR XML PATH | Widely supported, flexible formatting |
| Custom Formatting Needs | FOR XML PATH | Granular control over output structure |
This decision matrix helps you choose the right tool for the job. By understanding the strengths and weaknesses of each approach, you can make informed decisions that improve the performance and maintainability of your code.
Final Thoughts on SQL Server Serialization
FOR XML PATH is a powerful tool, but it is not a silver bullet. It requires a deep understanding of how SQL Server handles strings, XML, and queries. By mastering this feature, you can solve complex data serialization problems that would otherwise require complex application logic.
The key is to use it wisely. Don’t use it for simple tasks where STRING_AGG or FOR JSON will do the job. But when you need to build complex hierarchies or custom formats, FOR XML PATH is the engine that powers the solution.
Remember to always test your queries, handle edge cases, and consider the performance implications of your design. With the right approach, FOR XML PATH can be a valuable asset in your SQL Server toolkit.
Frequently Asked Questions
What is the difference between FOR XML PATH and FOR JSON?
FOR XML PATH generates XML strings, while FOR JSON generates valid JSON. FOR JSON is generally preferred for modern applications because it produces standard-compliant JSON. FOR XML PATH is often used for legacy systems or when custom formatting is required.
Can FOR XML PATH handle NULL values correctly?
No, FOR XML PATH treats NULL values as the text “NULL”. You must use ISNULL or COALESCE to replace NULLs with empty strings or default values before generating the XML.
Is FOR XML PATH supported in all versions of SQL Server?
Yes, FOR XML PATH has been supported in SQL Server since version 7.0. It is available in all modern versions, including SQL Server 2022.
How do I escape special characters in FOR XML PATH?
SQL Server does not automatically escape special characters like <, >, &, or ". You must manually escape them using REPLACE or use the xml() data type to handle the escaping automatically.
What is the performance impact of using FOR XML PATH in recursive queries?
Recursive queries using FOR XML PATH can have a significant performance impact, especially with large datasets. Each level of nesting adds overhead, and the query may need to be re-evaluated multiple times. Pre-computing the hierarchy is often a better solution.
Can I use FOR XML PATH to generate HTML tables?
Yes, FOR XML PATH is commonly used to generate HTML tables. You can construct the table structure using XML tags and then convert the result to HTML using string manipulation functions.
Further Reading: Microsoft Docs on FOR XML PATH
Newsletter
Get practical updates worth opening.
Join the list for new posts, launch updates, and future newsletter issues without spam or daily noise.

Leave a Reply