Search code examples
xmlt-sqlsql-server-2008-r2xquerysqlxml

Select XML nodes as attributes


I am assuming the answer to my question is going to be something simple which I can't figure out myself. Here is the scenario:

I am using SQL Server 2008 R2 where a table has an XML column where the data is saved in the following format:

<Person>
    <firstName>John</firstName>
    <lastName>Lewis</lastName>
</Person>

The Person node can have any number of child-nodes for which the element names might be different (not known beforehand). I am looking for a query to return an XML which has the values for all the nodes as attributes.

So the output for the above XML should be:

<Person firstName="John" lastName="Lewis"/>

I can't think of a query to get the above output. I don't want to use a query like

Select 
      PersonColumn.value('(/Person/firstName)[1]', 'varchar(100)') AS '@firstName'
    , PersonColumn.value('(/Person/lastName)[1]', 'varchar(100)') AS '@lastName'
FROM MyTable
WHERE MyTable.MyPrimaryKey=1
FOR XML PATH('Person'), TYPE

since I don't know what nodes might there be under the Person node.


Solution

  • I've tried to do this

    select
        PersonColumn.query('
            element Person {
                for $i in /Person/*
                return attribute {local-name($i)} {string($i)}
            }
        ')
    from MyTable
    

    but it turns out that it's impossible to use dynamic attribute names

    XQuery [MyTable.PersonColumn.query()]: Only constant expressions are supported for the name expression of computed element and attribute constructors.: select PersonColumn.query(' element Person { for $i in /Person/* return attribute {local-name($i)} {string($i)} } ') from MyTable
    

    So best I can do so far is

    select 
        cast(
            '<Person ' + 
            (
                select
                    PersonColumn.query('
                    for $i in /Person/*
                    return concat(local-name($i), "=""", data($i), """")
                    ').value('.', 'nvarchar(max)')
                for xml path('')
            ) + '/>'
        as xml)
    from MyTable
    

    It's also possible to do this

    select
        cast(
            '<Person ' + 
            PersonColumn.query('
                for $i in /Person/*
                return concat(local-name($i), "=""", data($i), """")
            ').value('.', 'nvarchar(max)') +
            '/>'
          as xml)
    from MyTable
    

    but it will not work if your data contains some characters like < > and so on