Search code examples
sql-serverxmlopenxml

How can I make a SQL table from an XML file that will have a dynamic number of nodes?


I'm using SQL Server 2008.
The task: take an XML file and parse it into a(n) SQL table.
The problem: The number of columns and their names will vary based on the XML.

Here's some code:

DECLARE @xmlData XML;  
SET @xmlData = '<root>
  <item id="1">
    <item_number>IT23</item_number>
    <title>Item number twenty-three</title>
    <setting>5 to 20</setting>
    <parameter>10 to 16</parameter>
  </item>
  <item id="2">
    <item_number>RJ12</item_number>
     <title>Another item with a 12</title>
     <setting>7 to 35</setting>
     <parameter>1 to 34</parameter>
  </item>
  <item id="3">
     <item_number>LN90</item_number>
     <title>LN with 90</title>
     <setting>3 to 35</setting>
     <parameter>9 to 50</parameter>
  </item>
</root>'

For example using the above XML, I would need a SQL table returned that would look like this:

table snapshot

Here's how I got the above table:

   DECLARE @idoc INT;
   EXEC sp_xml_preparedocument @idoc OUTPUT, @xmlData

   SELECT * 
   FROM  OPENXML (@idoc, '/root/item', 2)
   WITH (item_number VARCHAR(100),
         title VARCHAR(100),
         setting VARCHAR(100),
         parameter VARCHAR(100))

Now let's say the XML changed where each item node had a new child node with the name 'new_node'. Like so:

<root>
  <item id="1">
    <item_number>IT23</item_number>
    <title>Item number twenty-three</title>
    <setting>5 to 20</setting>
    <parameter>10 to 16</parameter>
    <new_node>data</new_node>
  </item>
  <item id="2">
    <item_number>RJ12</item_number>
    <title>Another item with a 12</title>
    <setting>7 to 35</setting>
    <parameter>1 to 34</parameter>
    <new_node>goes</new_node>
  </item>
  <item id="3">
    <item_number>LN90</item_number>
    <title>LN with 90</title>
    <setting>3 to 35</setting>
    <parameter>9 to 50</parameter>
    <new_node>here</new_node>
  </item>
</root>

I must change my code to include the new node:

   SELECT * 
   FROM  OPENXML (@idoc, '/root/item', 2)
   WITH (item_number VARCHAR(100),
         title VARCHAR(100),
         setting VARCHAR(100),
         parameter VARCHAR(100),
         new_node VARCHAR(100))

To get this table:

second table from XML

So the problem is that the child nodes of 'item' will vary.

How can I generate the same tables without specifying the columns? Is there some other approach than having to use OPENXML?


Solution

  • With a dynamic number of columns you need dynamic SQL.

    declare @XML xml = 
    '<root>
      <item id="1">
        <item_number>IT23</item_number>
        <title>Item number twenty-three</title>
        <setting>5 to 20</setting>
        <parameter>10 to 16</parameter>
        <new_node>data</new_node>
      </item>
      <item id="2">
        <item_number>RJ12</item_number>
        <title>Another item with a 12</title>
        <setting>7 to 35</setting>
        <parameter>1 to 34</parameter>
        <new_node>goes</new_node>
      </item>
      <item id="3">
        <item_number>LN90</item_number>
        <title>LN with 90</title>
        <setting>3 to 35</setting>
        <parameter>9 to 50</parameter>
        <new_node>here</new_node>
      </item>
    </root>'
    
    declare @SQL nvarchar(max) = ''
    declare @Col nvarchar(max) = ', T.N.value(''[COLNAME][1]'', ''varchar(100)'') as [COLNAME]' 
    
    select @SQL = @SQL + replace(@Col, '[COLNAME]', T.N.value('local-name(.)', 'sysname'))
    from @XML.nodes('/root/item[1]/*') as T(N)
    
    set @SQL = 'select '+stuff(@SQL, 1, 2, '')+' from @XML.nodes(''/root/item'') as T(N)' 
    
    exec sp_executesql @SQL, N'@XML xml', @XML