select multiple nodes xml

lance-p picture lance-p · Mar 13, 2013 · Viewed 8.9k times · Source

I'm retrieving xml formatted text from ntext fields (sample format of a row below):

<root>
  <DocInfo>
    <CompanyName>Some Company</CompanyName>
    <WebsiteUrl>http://www.someurl.com</WebsiteUrl>
    <PrimaryServices>Benefits Administration</PrimaryServices>
    <PrimaryServices>Payroll Processing</PrimaryServices>
    <SecondaryServices>Background Checking</SecondaryServices>
    <SecondaryServices>HR Outsourcing</SecondaryServices>
    <SecondaryServices>Comp & Benefits</SecondaryServices>
    <SecondaryServices>Administration</SecondaryServices>
  </DocInfo>
</root>

Using this sql I am retrieving the single node values:

select  @xmlString = COALESCE(@xmlString + '', '') + cast(content_html as nvarchar(max)) FROM  content where folder_id = 18
set @xmlString = replace(@xmlString,'<?xml version="1.0" encoding="UTF-16" standalone="yes"?>','')
set @XML = cast(@xmlString as xml)

Select
T.N.value('CompanyName[1]', 'varchar(250)') as CompanyName,
T.N.value('WebsiteUrl[1]', 'varchar(250)') as WebsiteUrl,
T.N.value('PrimaryServices[1]', 'varchar(250)') as PrimaryServices,
T.N.value('SecondaryServices[1]', 'varchar(250)') as SecondaryServices,
T.N.value('Description[1]', 'varchar(max)') as Description
from @XML.nodes('/root/DocInfo') as T(N)

This works fine for the single node values (CompanyName, WebsiteUrl). However, it isn't inserting the nodes with multiple values properly (like PrimaryServices and SecondaryServices - each of which may have zero to 16 nodes). How do I get these variable length multiple node values into these columns?

Thanks for any help

Answer

Mikael Eriksson picture Mikael Eriksson · Mar 13, 2013

To get the multiple nodes as a comma separated value you can use a variant of the for xml path('') trick. Use the shredded XML (T.N) as a source in the sub-query to get the nodes you are interested in. The xQuery ... substring(text()[1]) ... part is just there to remove the extra comma and to get the comma separated value out of the XML that is created by for xml.

select
  T.N.value('(CompanyName/text())[1]', 'varchar(250)') as CompanyName,
  T.N.value('(WebsiteUrl/text())[1]', 'varchar(250)') as WebsiteUrl,
  (
    select ', '+P.N.value('text()[1]', 'varchar(max)')
    from T.N.nodes('PrimaryServices') as P(N)
    for xml path(''), type
  ).value('substring(text()[1], 2)', 'varchar(max)') as PrimaryServices,
  (
    select ', '+S.N.value('text()[1]', 'varchar(max)')
    from T.N.nodes('SecondaryServices') as S(N)
    for xml path(''), type
  ).value('substring(text()[1], 2)', 'varchar(max)') as SecondaryServices,
  T.N.value('(Description/text())[1]', 'varchar(max)') as Description
from @XML.nodes('/root/DocInfo') as T(N)

If you want all the services in one column you can use a different xPath in the nodes part in the sub-query.

select
  T.N.value('(CompanyName/text())[1]', 'varchar(250)') as CompanyName,
  T.N.value('(WebsiteUrl/text())[1]', 'varchar(250)') as WebsiteUrl,
  (
    select ', '+P.N.value('text()[1]', 'varchar(max)')
    from T.N.nodes('PrimaryServices,SecondaryServices') as P(N)
    for xml path(''), type
  ).value('substring(text()[1], 2)', 'varchar(max)') as Services,
  T.N.value('(Description/text())[1]', 'varchar(max)') as Description
from @XML.nodes('/root/DocInfo') as T(N)