Skip to content

How to fix: Extract XML data from forms into tables - presence of namespaces in XML

When extracting data from XML blobs (often generated by electronic forms or SOAP-based web services) into relational tables, the query returns NULL values or empty rowsets despite the data being clearly visible in the raw XML.

This occurs because of XML Namespaces (xmlns). Namespaces are used to prevent element name conflicts by qualifying names with a URI. However, standard XPath queries in SQL (like nodes() or value() in T-SQL) are namespace-sensitive. If your XPath does not explicitly account for the namespace defined in the root or parent elements, the engine fails to resolve the path, resulting in a silent failure where no data is matched.

Cause Description
Default Namespace An xmlns="uri" attribute is present without a prefix. XPath treats unprefixed queries as being in the “null” namespace, causing a mismatch.
Namespace Prefix Mismatch The XML uses a prefix (e.g., xmlns:ns1="uri"), but the SQL query attempts to access elements without providing the mapping.
Implicit Inherited Namespaces Child elements inherit the namespace of the parent. Querying a child without referencing the parent’s namespace fails.
Case Sensitivity XML URIs are case-sensitive. A mismatch between http://schema.com and HTTP://schema.com will break the binding.

The most performant and “correct” way to handle namespaces in SQL Server is to declare them explicitly using the WITH XMLNAMESPACES clause.

-- Example XML with a default namespace
DECLARE @xml XML = '
<FormEntry xmlns="http://schemas.standard.com/form/v1">
<Field Name="Username">jdoe</Field>
<Field Name="Email">[email protected]</Field>
</FormEntry>';
-- The fix: Declare the namespace and map it to a prefix
WITH XMLNAMESPACES (DEFAULT 'http://schemas.standard.com/form/v1')
SELECT
T.c.value('@Name', 'VARCHAR(50)') AS FieldName,
T.c.value('.', 'VARCHAR(100)') AS FieldValue
FROM @xml.nodes('/FormEntry/Field') AS T(c);

If the form data uses specific prefixes (common in XFA or InfoPath forms), map them accordingly.

DECLARE @xml XML = '
<root xmlns:data="http://api.com/data">
<data:row id="101">Value A</data:row>
</root>';
WITH XMLNAMESPACES ('http://api.com/data' AS d)
SELECT
T.c.value('@id', 'INT') AS ID,
T.c.value('.', 'VARCHAR(50)') AS RowValue
FROM @xml.nodes('/root/d:row') AS T(c);

3. The Wildcard Namespace Approach (Quick Fix)

Section titled “3. The Wildcard Namespace Approach (Quick Fix)”

If the namespace URI changes frequently or you want to ignore namespaces entirely, use the *:local-name() syntax or the wildcard prefix. This is slightly less performant but highly flexible.

DECLARE @xml XML = '<ns1:Data xmlns:ns1="uri:test"><ns1:Item>Logic</ns1:Item></ns1:Data>';
-- Using the *: syntax to ignore the prefix
SELECT
T.c.value('.', 'VARCHAR(50)') AS ItemValue
FROM @xml.nodes('/*:Data/*:Item') AS T(c);

For extremely legacy systems where namespaces are malformed, you can cast the XML to NVARCHAR(MAX), strip the xmlns attributes via REPLACE, and cast back to XML. Use this only as a last resort.

DECLARE @rawXml NVARCHAR(MAX) = '<Form xmlns="http://bad-uri.com"><Val>123</Val></Form>';
-- Strip namespace string
SET @rawXml = CAST(REPLACE(CAST(@rawXml AS NVARCHAR(MAX)), 'xmlns="http://bad-uri.com"', '') AS NVARCHAR(MAX));
DECLARE @cleanXml XML = CAST(@rawXml AS XML);
SELECT @cleanXml.value('(/Form/Val)[1]', 'INT');
  1. Always Check the Root: Before writing your SQL, check the root element for xmlns attributes. Press Ctrl + F and search for “xmlns” in your XML sample.
  2. Use Aliases: Even if the XML uses a default namespace (no prefix), assign it an alias in your WITH XMLNAMESPACES clause to make the query more readable.
  3. Schema Bound Queries: In production environments, avoid using //* or *:. Explicitly defining the path with namespaces is significantly faster as it allows the XML index to work efficiently.
  4. Centralize Namespaces: If you have multiple stored procedures querying the same form type, consider creating a User Defined Function or a standard snippet to ensure namespace URIs are consistent across the codebase.