Search code examples
sqlsql-serverxmlxpathxml-dml

How to insert xml node in Sql Server without inserting empty namespace?


This is an example of how my source xml looks

<Catalog xmlns="http://schemas.example.com/stuff/stuff">
  <String Key="Name" Tag="22a41320-bb66-41a9-8806-760d13679c6c">Document Title 1</String>
  <String Key="Name" Tag="023463cf-9237-45b6-ac3f-621b9b09f609">Title for document 2</String>
</Catalog>

I plan to loop through the String nodes and transform them into Document nodes (this only shows the first iteration of the loop). However, when I insert the new node, it inserts an empty namespace. This is the result I get:

<Catalog xmlns="http://schemas.example.com/stuff/stuff">
  <String Key="Name" Tag="023463cf-9237-45b6-ac3f-621b9b09f609">Title for document 2</String>
  <Document xmlns="" Key="Document Title 1" Handle="22a41320-bb66-41a9-8806-760d13679c6c" />
</Catalog>

Notice the empty namespace. I want to omit the namespace on the Document node entirely.

Here's the result I want

<Catalog xmlns="http://schemas.example.com/stuff/stuff">
  <String Key="Name" Tag="023463cf-9237-45b6-ac3f-621b9b09f609">Title for document 2</String>
  <Document Key="Document Title 1" Handle="22a41320-bb66-41a9-8806-760d13679c6c" />
</Catalog>

Here is a full query you can play with:

declare @temp xml, @newNode xml;

set @temp = cast(
'<Catalog xmlns="http://schemas.example.com/stuff/stuff">
  <String Key="Name" Tag="22a41320-bb66-41a9-8806-760d13679c6c">Document Title 1</String>
  <String Key="Name" Tag="023463cf-9237-45b6-ac3f-621b9b09f609">Title for document 2</String>
</Catalog>' as xml)

select 'before', @temp

set @newNode = CAST(
  '<Document Key="' + @temp.value('declare default element namespace "http://schemas.example.com/stuff/stuff"; (/Catalog/String/text())[1]', 'varchar(max)') + 
  '" Handle="' + @temp.value('declare default element namespace "http://schemas.example.com/stuff/stuff"; (/Catalog/String/@Tag)[1]', 'varchar(50)') + '"  />' 
as xml)

set @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; insert sql:variable("@newNode") into (/Catalog)[1] ')
set @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; delete (/Catalog/String)[1]')

select 'after', @temp

Solution

  • I tried various methods to get around this

    • using with xmlnamespaces: no change
    • using explicitly defined namespaces: no change
    • using the same namespace as the parent: results in the default namespace being inserted into the Document node
    • deleting the empty namespace with modify/delete: would not remove the xmlns attribute
    • inserting the values dynamically inside the modify/insert : "The argument 1 of the XML data type method "modify" must be a string literal."

    Solution

    So the last error got me thinking, it will insert the node I want with no namespace so long as it's a string literal.. So I did just that.

    1. Insert empty node with empty attributes
    2. Use modify/replace to fill in the values of the attributes after insertion

    And here's the example of what it looks like

    declare @temp xml
    
    set @temp = cast(
    '<Catalog xmlns="http://schemas.example.com/stuff/stuff">
      <String Key="Name" Tag="22a41320-bb66-41a9-8806-760d13679c6c">Document Title 1</String>
      <String Key="Name" Tag="023463cf-9237-45b6-ac3f-621b9b09f609">Title for document 2</String>
    </Catalog>' as xml)
    
    select 'before', @temp
    
    while (@temp.value('declare default element namespace "http://schemas.example.com/stuff/stuff"; count(/Catalog/String)', 'int') > 0)
    begin
    SET @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; insert <Document Key="" Handle="" /> into (/Catalog)[1] ')
    SET @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; replace value of (/Catalog/Document[@Handle=""]/@Handle)[1] with (/Catalog/String/@Tag)[1]')
    SET @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; replace value of (/Catalog/Document[@Key=""]/@Key)[1] with (/Catalog/String/text())[1]')
    SET @temp.modify('declare default element namespace "http://schemas.example.com/stuff/stuff"; delete (/Catalog/String)[1]')
    end
    
    select 'after', @temp