2

I have an xml document that's pretty straightfoward. The only difference is that the elements can change. One time I might have:

<data><PersonalInfo>
<Person><FirstName>Bob</FirstName><LastName>Smith</LastName></Person>
<Person><FirstName>John</FirstName><LastName>Doe</LastName></Person>
</PersonalInfo></data>

The next time I might have:

<data><AddressInfo>
<Address><City>Cleveland</City><State>OH</State></Address>
<Address><City>Chicago</City><State>IL</State></Address>
</AddressInfo></data>

I would like to write a select statement that produces a dynamic table depending on which xml doc I've got at the moment.

Ex: For the first one:

First Name     Last Name
------------------------
Bob             Smith
John            Doe
Etc...

For the 2nd one

City       State
-----------------------
Cleveland   OH
Chicago     IL
Etc...

The 2 examples are not related in any way (Bob is not from Cleveland, etc...)

I just want to use the same code to produce both tables...depending on the xml doc. The only difference will be the node reference, of course:

Example 1:  data/PersonalInfo/Person*
Example 2:  data/AddressInfo/Address*

I do not want to combine or change anything in the xml doc structure. They are what they are coming in. How can I reference each one to create the two different tables above - each xml doc coming in will be in a separate run stored procedure. But it will be the same stored procedure. Any assistance is greatly appreciated, thanks in advance!

  • I forgot to mention - I do NOT want to hardode the field names in the code that produces the table. I want the code to know the field name from the xml doc and create it, if possible! – Daniel Raymond Patfield Jan 13 '16 at 16:55

2 Answers2

5

Try it like this:

CREATE PROCEDURE dbo.TransformPlainXML(@InputXml XML)
AS
BEGIN

DECLARE @PivotColumns NVARCHAR(MAX);

WITH DistinctElementNames AS
(
    SELECT DISTINCT '[' + Element.value('fn:local-name(.)','varchar(max)') + ']' AS ElementName
    FROM @InputXml.nodes('/data/*/*/*') As One(Element)
)
SELECT @PivotColumns = STUFF(
(
    SELECT ',' + ElementName
    FROM DistinctElementNames
    FOR XML PATH('')
),1,1,'');

DECLARE @cmd NVARCHAR(MAX)=
'WITH Lines AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowIndex
          ,Line.query(''.'') AS OneLine
    FROM @xml.nodes(''/data/*/*'') As One(Line)
)
SELECT p.*
FROM
(
    SELECT RowIndex
          ,Element.value(''.[1]'',''varchar(max)'') AS ElementValue
          ,Element.value(''fn:local-name(.)'',''varchar(max)'') AS ElementName
    FROM Lines
    CROSS APPLY OneLine.nodes(''./*/*'') AS The(Element)
) AS tbl
PIVOT
(
    MIN(ElementValue) FOR ElementName IN(' + @PivotColumns + ')
) AS p
';

EXECUTE sp_executesql @cmd,N'@xml XML',@xml=@InputXml;
END
GO

And test it like this

declare @xml1 XML=
'<data>
  <PersonalInfo>
    <Person>
      <FirstName>Bob</FirstName>
      <LastName>Smith</LastName>
    </Person>
    <Person>
      <FirstName>John</FirstName>
      <LastName>Doe</LastName>
    </Person>
  </PersonalInfo>
</data>';


EXEC TransformPlainXML @xml1;

declare @xml2 XML=
'<data>
  <AddressInfo>
    <Address>
      <City>Cleveland</City>
      <State>OH</State>
    </Address>
    <Address>
      <City>Chicago</City>
      <State>IL</State>
    </Address>
  </AddressInfo>
</data>';
EXEC TransformPlainXML @xml2;

The two results:

RowIndex    FirstName   LastName
1           Bob         Smith
2           John        Doe

And

RowIndex    City        State
1           Cleveland   OH
2           Chicago     IL
Shnugo
  • 66,100
  • 9
  • 53
  • 114
  • I ALMOST got excited and ready to use it, when I got the following error: "Incorrect syntax near 'PIVOT'. You may need to set the compatibility level of the current database to a higher value to enable this feature. See help for the SET COMPATIBILITY_LEVEL option of ALTER DATABASE." We're using 2008 R2 and sadly, I can't alter compatibility until I know more about doing that. But it looked like great code! ;-( – Daniel Raymond Patfield Jan 19 '16 at 18:15
  • 2
    @DanielRaymondPatfield, `PIVOT` was introduced [with SQL Server 2005 already](https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx)... Be aware, that "using 2008 R2" doesn't mean that your actual database is fully running as 2008R2. Given an old SS2000 db which is opened from a BAK-file in SS2008R2 this will still know, that it is SS2000 under the hood. 1) Run my script in the context of the master db. 2) Look into the db options and find the actual compatibility level which should be 90 or higher. – Shnugo Jan 19 '16 at 19:35
  • My apologies - I thought I was doing the voting thing correctly, but apparently not. As for resolution - I have not yet had the time to check into compatibility issues - see my previous comment, please. – Daniel Raymond Patfield Feb 08 '16 at 21:46
  • I think it's a great solution - if I get my compatibility issues covered, I have faith that this will perform and do the things I was looking for. As for voting, what do I need to do to elevate your solution via vote. I thought I put it at number one, but again, I'm new here... – Daniel Raymond Patfield Feb 09 '16 at 14:19
  • Does this solution derive the schema from the first row, and expect all others will share the same elements? – tbone Feb 07 '20 at 00:21
1

You could create a column mapping table, that stores any amount of 'mapping templates'...(for lack of a better description)

So that your new table has values:

TYPE        ORDER            COLUMNNAME
Person      1                FirstName
Person      2                LastName
Address     1                City
Address     2                State

And you use this table to load your actual xml node names and use a loop to collect the values from XML.

Is this feasible? Do you have access to make and read this table in SQL?

Grantly
  • 2,546
  • 2
  • 21
  • 31
  • I appreciate the reply, however I want to get away from any other objects. I've since resorted to producing a temp table with field name/value, but I'd rather create a table where the column names are dynamically created horizontally (per my above examples) instead of vertically. – Daniel Raymond Patfield Jan 16 '16 at 16:20
  • 1
    What about using another XML file to control your mapping? You can create your 'table' of data (same as the answer above) in an XML file... Load this into memory and use this to create your XML output with any number of columns - specified in the XML file – Grantly Jan 16 '16 at 16:38
  • Another good answer - and we do things like this a lot. But I was hoping there was a piece of parsing xml code out there that would do the trick. Just don't know what it is. Something to simply look at the tag elements and place them as a table. I can currently place them in a table with columns "Node Name" and "Node Value" - but I'd prefer a horizontal table dynamically created. – Daniel Raymond Patfield Jan 17 '16 at 16:50
  • With the node name and node value - I'm manipulating the data appropriately in my procedure, but again... – Daniel Raymond Patfield Jan 17 '16 at 16:51
  • 1
    @Grantly, it is possible to get all information directly from the given XML and use it with dynamic SQL - as long as it follows the same quite plain structure. – Shnugo Jan 19 '16 at 16:04