How to return multiple values from XML element in SQL? - sql

I need to pull information from the "Name" element from an XML column in SQL. An example of the XML is below:
<ArrayOfTarget xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/TriTech.InformRMS.Domain.Core.ComplexTypes">
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true" />
<Id>19bc33e1-a788-cd99-3dab-08d92aa7d030</Id>
<Name>Case Number WB21-006637</Name>
<Type>Case Management</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true" />
<Id>cb4d829b-c31a-cadb-5c9e-08d934b7404d</Id>
<Name>Incident Supplement Number WB21-006637.006</Name>
<Type>Data Entry</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true" />
<Id>6b23195c-4650-c0c9-925d-08d71a88f611</Id>
<Name i:nil="true" />
<Type>Template</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true" />
<Id>b465517c-5926-c6b3-1cc6-08d6fbe6da27</Id>
<Name>Default Workflow</Name>
<Type>Workflow</Type>
</Target>
<Target>
<AgencyId i:nil="true" />
<AgencyName i:nil="true" />
<Id i:nil="true" />
<Name>Complete</Name>
<Type>Workflow Step</Type>
</Target>
</ArrayOfTarget>
I have this SQL Query which works for returning five of the "Name" elements:
;WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema-instance' AS i,
'http://schemas.datacontract.org/2004/07/TriTech.InformRMS.Domain.Core.ComplexTypes' AS s)
SELECT TOP 100
TargetData_Xml,
Description,
TargetData_Xml.value('(/s:ArrayOfTarget/s:Target/s:Name)[1]', 'varchar(100)') as Context1,
TargetData_Xml.value('(/s:ArrayOfTarget/s:Target/s:Name)[2]', 'varchar(100)') as Context2,
TargetData_Xml.value('(/s:ArrayOfTarget/s:Target/s:Name)[3]', 'varchar(100)') as Context3,
TargetData_Xml.value('(/s:ArrayOfTarget/s:Target/s:Name)[4]', 'varchar(100)') as Context4,
TargetData_Xml.value('(/s:ArrayOfTarget/s:Target/s:Name)[5]', 'varchar(100)') as Context5
FROM
[InformRMSAudit].[dbo].[AuditEntities]
WHERE
CaseNumber = 'RP21-010802'
ORDER BY
Date desc
That would be sufficient if every record had only five "Name" elements in the XML, but the number of "Name" elements varies from record to record.
How could I change my query to handle the variation from record to record?

Please try the following solution.
As #Larnu pointed out, it is much better to shred the XML as rows.
If needed it is very easy to filter out names with NULL values.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, TargetData_Xml XML);
INSERT INTO #tbl (TargetData_Xml) VALUES
(N'<ArrayOfTarget xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/TriTech.InformRMS.Domain.Core.ComplexTypes">
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true"/>
<Id>19bc33e1-a788-cd99-3dab-08d92aa7d030</Id>
<Name>Case Number WB21-006637</Name>
<Type>Case Management</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true"/>
<Id>cb4d829b-c31a-cadb-5c9e-08d934b7404d</Id>
<Name>Incident Supplement Number WB21-006637.006</Name>
<Type>Data Entry</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true"/>
<Id>6b23195c-4650-c0c9-925d-08d71a88f611</Id>
<Name i:nil="true"/>
<Type>Template</Type>
</Target>
<Target>
<AgencyId>ca2fa1dd-2cd4-c219-bea5-08d6fbe6d96c</AgencyId>
<AgencyName i:nil="true"/>
<Id>b465517c-5926-c6b3-1cc6-08d6fbe6da27</Id>
<Name>Default Workflow</Name>
<Type>Workflow</Type>
</Target>
<Target>
<AgencyId i:nil="true"/>
<AgencyName i:nil="true"/>
<Id i:nil="true"/>
<Name>Complete</Name>
<Type>Workflow Step</Type>
</Target>
</ArrayOfTarget>');
-- DDL and sample data population, end
WITH XMLNAMESPACES (DEFAULT 'http://schemas.datacontract.org/2004/07/TriTech.InformRMS.Domain.Core.ComplexTypes')
SELECT ID
, c.value('(Name/text())[1]', 'VARCHAR(100)') AS [Name]
FROM #tbl
CROSS APPLY TargetData_Xml.nodes('/ArrayOfTarget/Target') AS t(c);
Output
+----+--------------------------------------------+
| ID | Name |
+----+--------------------------------------------+
| 1 | Case Number WB21-006637 |
| 1 | Incident Supplement Number WB21-006637.006 |
| 1 | NULL |
| 1 | Default Workflow |
| 1 | Complete |
+----+--------------------------------------------+

Related

SQL string value

Have a large XML file stored in a field within a table, many values have already been extracted and stored in the table, but I'm looking to capture (2) additional: account type = "current" status="X" and account type = "former" status ="Y". In the partial output below there is no former account type so I need a strategy for missing as well.
<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://cp.com/rules/client">
<admin>
<product_reference>12345678901234</product_reference>
<report_type>XXXXXXX</report_type>
<status>XXXXXXXX</status>
<ownership>XXXXXXX</ownership>
<report_code>1234</report_code>
<report_description>XXXXXXXXXXXXXXXXX</report_description>
<purpose>XXXXXXXX</purpose>
<date_request_ordered>mm/dd/yyyy</date_request_ordered>
<date_request_received>mm/dd/yyyy</date_request_received>
<date_request_completed>mm/dd/yyyy</date_request_completed>
<time_report_processed>01234</time_report_processed>
<multiple_scores_ordered>false</multiple_scores_ordered>
<vendor name="XXXXXXXXXXXXX" address="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" />
<report>
<sequence>0000000000</sequence>
<count>0000000000</count>
</report>
</admin>
<report>
<alerts_scoring>
<scoring>
<score status="XXXXXXXXXX">
<model_label>XXXXXXXXXXXXXXXXX</model_label>
<score>123</score>
<rating_state>XX</rating_state>
<classification>XXXXXXXXXXXXXXXXX</classification>
<reason_codes>
<code>05</code>
<description>XXXXXXXXXXXXXXXXX</description>
</reason_codes>
<reason_codes>
<code>04</code>
<description>XXXXXXXXXXXXXXXXX</description>
</reason_codes>
<reason_codes>
<code>10</code>
<description>XXXXXXXXXXXXXXXXX</description>
</reason_codes>
<reason_codes>
<code>27</code>
<description>XXXXXXXXXXXXXXXXX</description>
</reason_codes>
</score>
</scoring>
<general>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</general>
<general>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</general>
<general>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</general>
</alerts_scoring>
<vendor_dataset>
<subjects>
<subject type="Primary" relationship_to_data="Subject">
<name type="Report Subject">
<first>XXXX</first>
<middle>X</middle>
<last>XXXX</last>
</name>
<name type="Alias">
<first>XXXXXXXXXX</first>
<last>XXXXXXXX</last>
</name>
<birth_date>mm/dd/yyyy</birth_date>
<ssn>999999999</ssn>
<address type="residence" ref="1" />
<address type="former" ref="2" />
<address type="former" ref="3" />
</subject>
</subjects>
<addresses>
<address id="1">
<house>1234</house>
<street1>sample</street1>
<city>sample</city>
<state>XX</state>
<postalcode>12345</postalcode>
<zip4>1234</zip4>
<date_first_at_address>mm/dd/yyyy</date_first_at_address>
<date_last_at_address>mm/dd/yyyy</date_last_at_address>
</address>
<address id="X">
<house>1234</house>
<street1>XXXXXXXXX</street1>
<city>XXXXXXXXX</city>
<state>XX</state>
<postalcode>12345</postalcode>
<zip4>1234</zip4>
<date_first_at_address>mm/dd/yyyy</date_first_at_address>
<date_last_at_address>mm/dd/yyyy</date_last_at_address>
</address>
</addresses>
</vendor_dataset>
<summary>
<date_oldest_trade>mm/dd/yyyy</date_oldest_trade>
<date_latest_trade>mm/dd/yyyy</date_latest_trade>
<date_latest_activity>mm/dd/yyyy</date_latest_activity>
<includes_bankruptcies flag="false" />
<includes_other_records public_records="false" collection="false" consumer_statement="false" />
<credit_range high="12345" low="123" number_trade_lines="123" />
<account_status_counters>
<!-- here --> <account type="current" description="Pays Account as Agreed" status="1">12</account>
</account_status_counters>
<account_summaries>
<account type="Open-ended">
<number_accounts>0</number_accounts>
<total_owed>0</total_owed>
<total_past_due>0</total_past_due>
<high_amount>0</high_amount>
</account>
<account type="Revolving">
<number_accounts>00</number_accounts>
<total_owed>1234</total_owed>
<total_past_due>0</total_past_due>
<high_amount>12345</high_amount>
</account>
<account type="Installment">
<number_accounts>00</number_accounts>
<total_owed>12345</total_owed>
<total_past_due>0</total_past_due>
<high_amount>123456</high_amount>
</account>
</account_summaries>
<inquiry_history count="0" />
</summary>
<employment_history>
<employment_primary_subject>
<job entry="current" indirectly_verified="false">
<employer>
<name>XXXXXXXXXXX</name>
</employer>
</job>
<job entry="first_former" indirectly_verified="false">
<employer>
<name>XXXXXXXX</name>
<city>XXXXXXX</city>
<state>XX</state>
</employer>
</job>
</employment_primary_subject>
</employment_history>
<trade_account_activity>
<credit_trades>
<credit_trade automated_tape_supplier="false">
<reporting_member>
<number>1234X1234</number>
<name>XXX/1234</name>
</reporting_member>
<account>
<type>XXXXXXXXX</type>
<terms>XXX</terms>
<months_reviewed>00</months_reviewed>
<designator>XXXXXXXX(XXXXX)</designator>
</account>
<date_reported>mm/dd/yyyy</date_reported>
<date_opened>mm/dd/yyyy</date_opened>
<date_last_activity>mm/dd/yyyy</date_last_activity>
<current_rate>XXXXXXXXXXXXXXXXX</current_rate>
<highest_amount>1234</highest_amount>
<balance_amount>00</balance_amount>
<past_due_amount>00</past_due_amount>
<messages>
<message code="XX">XXXXXXXXXXXXXX</message>
<message code="XX">XXXXXXXXXXXXXX</message>
</messages>
</credit_trade>
</account>
<date_reported>mm/dd/yyyy</date_reported>
<date_opened>mm/dd/yyyy</date_opened>
<date_last_activity>mm/dd/yyyy</date_last_activity>
<current_rate>XXXXXXXXXXXXXXXXXXXXXX</current_rate>
<highest_amount>123456</highest_amount>
<balance_amount>123456</balance_amount>
<past_due_amount>0</past_due_amount>
<messages>
<message code="XX">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</message>
</messages>
</credit_trade>
</credit_trades>
</trade_account_activity>
<inquiry_history>
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX" member="12345X1234" />
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX" member="12345Y1234" />
<inquiry date="mm/dd/yyyy" name="CXXXXXXXXXXXXXXXXXXXXXXX" member="12345Z1234" />
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX & X" member="12345W1234" />
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX" member="12345V1234" />
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX" member="12345U1234" />
<inquiry date="mm/dd/yyyy" name="XXXXXXXXXXXXXXXXXXXXXXX" member="12345T1234" />
</inquiry_history>
</report>
</ncf_report>
I'm looking to extract the X value from account type = "current" status="X" and and Y value if an account type = "former" exists. In this case the value 1. added to XML to highlight area of interest. I started by pairing down the data set into a temp table.
select id,
LEFT(SUBSTRING(CreditscoreXML,charindex('<account type="current"',CreditscoreXML),charindex('</account>',CreditscoreXML)),charindex('">',SUBSTRING(CreditscoreXML,charindex('<account type="current"',CreditscoreXML),charindex('</account>',CreditscoreXML)))) [Current_Status]
select
Current_Status, --just so I see output is correct in temp table
substring(Current_Status, charindex('status="',
Current_Status)+8,len(Current_Status)-charindex('status',Current_Status)) [Current_Status]
from #TempCurrent
From here I further tried to refine the text search. Trying to figure out how to eliminate the " after 1 or a better solution to extract both current and former status, former can be missing, need this grouped by Id.
Current Output
Current_Worse_Score Current_Worse_Score Former_Worse
Original Text 1"
Rather than manipulating string data try using the built-in XML functions in SQL Server to make your life easier. For example:
create table dbo.Foo (
id int not null,
bar xml not null
);
insert dbo.Foo (id, bar) values (47, N'<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://cp.com/rules/client">
<!-- the rest of your xml... -->
</ncf_report>')
;with xmlnamespaces(default 'http://cp.com/rules/client')
select
id,
x.a.value(N'#description', N'nvarchar(50)') as [Description],
x.a.value(N'#status', N'nvarchar(50)') as [Status],
x.a.value(N'.', N'nvarchar(50)') as [Account]
from dbo.Foo
cross apply bar.nodes(N'/ncf_report/report/summary/account_status_counters/account[#type="current"]') x(a)
Which yields the result...
id Description Status Account
47 Pays Account as Agreed 1 12
You can use the built-in XML data type methods to query required values from an XML instance which is stored as XMLType column.
DECLARE #X XML;
SET #X = '<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" ....>'; -- provided XML instance
CREATE TABLE NCFREPORT
(
ncfreportcol XML NOT NULL
);
INSERT INTO ncfreport (ncfreportcol) values (#X); -- inserting the XML instance stored temporarily in above variable X
WITH xmlnamespaces ('http://cp.com/rules/client' as NR)
SELECT T.acc.value('(#status)[1]', 'int') AS Status,
T.acc.value('(#type)[1]', 'varchar(20)') AS AccType,
T.acc.value('(text())[1]', 'int') AS Acc
FROM ncfreport cross apply ncfreport.ncfreportcol.nodes ('/NR:ncf_report/NR:report/NR:summary/NR:account_status_counters/NR:account') as t(acc);
This will result in the following output:
Status AccType Acc
1 current 12
It will produce one row in the output for each account if you have multiple account tags defined in the XML instance. I also noticed that there are missing opening or closing tags in the above XML fragment. It would be a good idea to also have a look at validating the XML before entering into the table. Please have a look at various XML data type methods here - https://learn.microsoft.com/en-us/sql/t-sql/xml/xml-data-type-methods?view=sql-server-ver15

Find the value from XML data in SQL Server?

I want to find the value from XML data in SQL Server table.
Below is my sample xmldata;
<wddxPacket version="1.0">
<header />
<data>
<struct type="xyz">
<var name="TXRGHC43">
<string />
</var>
<var name="TWBS1">
<string>9011750</string>
</var>
<var name="PMNAMEID">
<string>2323443</string>
</var>
<var name="EDATE36">
<string />
</var>
<var name="TWBSDESC40">
<string />
</var>
</struct>
</data>
</wddxPacket>
I am searching for 9011750 under TWBS1. can you please help me on this. how to find 9011750 value.
I am trying the following queries but I didn't get any output.
select
col
from
xyz
where
col.value('(/wddxPacket/data/struct/var[TWBS1])[2]', 'varchar(max)') like '9011750'
SELECT col.value('(/wddxPacket/data/struct/var/string)[3]', 'varchar(100)')
FROM xyz
my requirement is to search the data entire table, that is only sample xml data.
Please try the following SQL. It shows how to use a predicate in the XPath expression to simulate WHERE clause.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata)
VALUES
(N'<wddxPacket version="1.0">
<header/>
<data>
<struct type="xyz">
<var name="TXRGHC43">
<string/>
</var>
<var name="TWBS1">
<string>9011750</string>
</var>
<var name="PMNAMEID">
<string>2323443</string>
</var>
<var name="EDATE36">
<string/>
</var>
<var name="TWBSDESC40">
<string/>
</var>
</struct>
</data>
</wddxPacket>');
-- DDL and sample data population, end
DECLARE #varName VARCHAR(20) = 'TWBS1';
SELECT c.value('(./text())[1]','INT') AS [string_value]
FROM #tbl AS tbl
CROSS APPLY tbl.xmldata.nodes('/wddxPacket/data/struct/var[#name=sql:variable("#varName")]/string') AS t(c);
Output
+--------------+
| string_value |
+--------------+
| 9011750 |
+--------------+

Stripping data from xml in SQL Server

One of my tables with xml datatype has the following xml information:
<RequestMetaData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MetaData Type="DocImport">
<Keywords>
<Key Name="Zone" Value="MIO" />
<Key Name="ClassificationStrategy" Value="NeedClassification" />
<Key Name="Folder" Value="0456e6ca" />
</Keywords>
</MetaData>
<MetaData Type="SourceResponse">
<Keywords>
<Key Name="NotificationResponse_20180427-150426" Value="Received successful response from Source" />
</Keywords>
</MetaData>
</RequestMetaData>
I need to write an SQL query to fetch the value of Classification strategy based on key name.
I have added the xml in a variable #xml and used the following code. It is returning NULL.
select A.b.value('ClassificationStrategy[1]', 'VARCHAR(30)') AS CS
FROM #xml.nodes('/RequestMetaData/MetaData/Keywords') AS A(b)
Can someone please help me with this.
You can read your XML in various ways. Use a simple .value() with an XPath/XQuery expression to retrieve a single value, use .query to retrieve a part of the XML or use .nodes() to return repeated elements as derived table:
DECLARE #xml XML=
N'<RequestMetaData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MetaData Type="DocImport">
<Keywords>
<Key Name="Zone" Value="MIO" />
<Key Name="ClassificationStrategy" Value="NeedClassification" />
<Key Name="Folder" Value="0456e6ca" />
</Keywords>
</MetaData>
<MetaData Type="SourceResponse">
<Keywords>
<Key Name="NotificationResponse_20180427-150426" Value="Received successful response from Source" />
</Keywords>
</MetaData>
</RequestMetaData>';
--Read the whole lot
SELECT md.value('#Type','nvarchar(max)') AS MetaDataType
,k.value('#Name','nvarchar(max)') AS KeyName
,k.value('#Value','nvarchar(max)') AS KeyValue
FROM #xml.nodes('/RequestMetaData/MetaData') A(md)
OUTER APPLY md.nodes('Keywords/Key') B(k);
--Get one key's value by name (anywhere in the doc)
DECLARE #keyName VARCHAR(100)='ClassificationStrategy';
SELECT #xml.value('(//Key[#Name=sql:variable("#keyName")]/#Value)[1]','nvarchar(max)');
--Use the meta data type as additional filter (if key names are not unique per doc)
DECLARE #kName VARCHAR(100)='ClassificationStrategy';
DECLARE #mdType VARCHAR(100)='DocImport';
SELECT #xml.value('(/RequestMetaData
/MetaData[#Type=sql:variable("#mdType")]
/Keywords
/Key[#Name=sql:variable("#kName")]
/#Value)[1]','nvarchar(max)');

Parsing XML in SQL without a namespace

The code will be self explanatory to the right person for this, but any questions please shout...
Thanks,
Dave
DECLARE #XML XML
SET #XML =
'<?xml version="1.0" encoding="utf-8"?>
<updates>
<versions>
<installer type="A" Xversion="101" iniSizeInBytes="22480" dataSizeInBytes="23396349" msiSizeInBytes="4732928" />
<installer type="B" Yversion="201" iniSizeInBytes="22480" dataSizeInBytes="116687353" msiSizeInBytes="5807616" webconfigModifierSizeInBytes="11800" />
<installer type="A" Xversion="102" iniSizeInBytes="22480" dataSizeInBytes="23396349" msiSizeInBytes="4732928" />
<installer type="B" Yversion="202" iniSizeInBytes="22480" dataSizeInBytes="116687353" msiSizeInBytes="5807616" webconfigModifierSizeInBytes="11800" />
</versions>
<update setNumber="1" XVersion="101" YVersion="201">
<detail Ref="1000">some detail info for 101 and 201</detail>
</update>
<update setNumber="2" XVersion="102" YVersion="202">
<detail Ref="1001">some detail info for 102 and 202</detail>
</update>
</updates>
'
SELECT
r.value('#ref','NVARCHAR(250)') as 'Ref', --This is wrong, but you can probably see i'm wanting the value of Ref, eg 1000 for line 1, 1001 for line 2
t.r.query('./detail').value('.','nvarchar(max)') as 'Detail'
FROM #XML.nodes('/updates/update') AS t(r);
You need detail element in value method to get the value in Ref attribute
SELECT
r.value('#Ref','NVARCHAR(250)') as 'Ref', -- It should be #Ref instead of #ref
r.query('.').value('.','nvarchar(max)') as 'Detail'
FROM #XML.nodes('/updates/update/detail') AS t(r);
Note : Elements and attributes in xml is case sensitive you cannot use #ref in query when xml attribute is Ref
Rextester Demo

SQL FOR XML multilevel from one pivoted table

I've been trying to use FOR XML without success to do the following.
Source table:
Country | ID | 1950 | 1955
-----------------------------------------------------
Country 1 | 1 | 2.43 | 2.55
Country 2 | 2 | 4.54 | 42.15
Desired output:
<locations>
<location>
<loc name='Country 1' id='1' />
<dub>
<data year='1950' value='2.43' />
<data year='1955' value='2.55' />
</dub>
</location>
<location>
<loc name='Country 2' id='2' />
<dub>
<data year='1950' value='4.54' />
<data year='1955' value='42.15' />
</dub>
</location>
</locations>
Will it be necessary to unpivot for the dub element? I wanted the simplest SQL query possible.
I think FOR XML is too difficult to use. You should be able to specify the hierarchy just using simple XPath on column names but it won't accept, for example, [dub/data/#year=1955/#value] as the name of the column [1950].
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
Country varchar(20),
ID int,
[1950] numeric(5,2),
[1955] numeric(5,2)
)
insert into YourTable values
('Country 1', 1, 2.43, 2.55),
('Country 2', 2, 4.54, 42.15)
Query 1:
select T.Country as 'loc/#name',
T.ID as 'loc/#id',
(
select 1950 as 'data/#year',
T.[1950] as 'data/#value',
null,
1955 as 'data/#year',
T.[1955] as 'data/#value'
for xml path(''), type
) as dub
from YourTable as T
for xml path('location'), root('locations'), type
Results:
<locations>
<location>
<loc name="Country 1" id="1" />
<dub>
<data year="1950" value="2.43" />
<data year="1955" value="2.55" />
</dub>
</location>
<location>
<loc name="Country 2" id="2" />
<dub>
<data year="1950" value="4.54" />
<data year="1955" value="42.15" />
</dub>
</location>
</locations>