How to loop xml in store procedure - sql-server

I have a store procedure that returns the below XML:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<ns2:ReportResponse>
<ns2:responseTitle />
<ns2:responseBody>
<ns2:resultRow>
<ns2:result Name="country" Value="United Kingdom" />
<ns2:result Name="code" Value="7360" />
</ns2:resultRow>
<ns2:resultRow>
<ns2:result Name="country" Value="France" />
<ns2:result Name="code" Value="7340" />
</ns2:resultRow>
</ns2:responseBody>
</ns2:ReportResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I want to be able to save the 2 records in the table, how can I get a loop to get the data?
Record 1:
Country=United Kingdom
Code=7360
Record 2:
Country=France
Code=7340
I tried to use this select but it's not returning anything.
SELECT
Record.value('#Name','VARCHAR')
FROM #XmlResponse.nodes('/Envelope/Body/ReportResponse/responseBody/resultRow')AS TEMPTABLE(Record)
Thanks.

Like this:
declare #doc xml = '
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<ns2:ReportResponse xmlns:ns2="http://whatever">
<ns2:responseTitle />
<ns2:responseBody>
<ns2:resultRow>
<ns2:result Name="country" Value="United Kingdom" />
<ns2:result Name="code" Value="7360" />
</ns2:resultRow>
<ns2:resultRow>
<ns2:result Name="country" Value="France" />
<ns2:result Name="code" Value="7340" />
</ns2:resultRow>
</ns2:responseBody>
</ns2:ReportResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
';
WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' as soap ,
'http://whatever' as ns2)
SELECT
Record.value('(ns2:result[#Name="country"])[1]/#Value','VARCHAR(20)') Country,
Record.value('(ns2:result[#Name="code"])[1]/#Value','int') Code
FROM #doc.nodes('/soap:Envelope/soap:Body/ns2:ReportResponse/ns2:responseBody/ns2:resultRow')AS TEMPTABLE(Record)
outputs
Country Code
-------------------- -----------
United Kingdom 7360
France 7340
(2 rows affected)

Related

How can I dynamically change the XML structure of a string in SQL

I need a SQL script that will to pull an XML string from the DB [varchar(max)], inspect it, and update it if it fits a specific situation.
Imagine that my xml is in the following format:
<root>
<level1>
<level2>
<level3 />
<level3 />
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="now is the time for XYZ">
<options>
<option this="that" />
<option me="you" />
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="this one is not of interest">
<options>
<option this="that" />
<option me="you" />
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="now is the time for ABC">
<options>
<option this="that" />
<option me="you" />
<option here="now" />
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
</root>
So, what I want to do is to update all elements whose name is "level6" and which have an attribute called "here" whose value begins with "now is the time". So, that should match just two elements above.
But, that's not the only selection criteria. The list of options must not contain <option here="now" />. So, that should leave us with just one element to update.
<level6 here="now is the time for XYZ">
<options>
<option this="that" />
<option me="you" />
</options>
</level6>
To that element, I then want to add the missing <option here="now" />, so that becomes:
<level6 here="now is the time for XYZ">
<options>
<option this="that" />
<option me="you" />
<option here="now" />
</options>
</level6>
So, the end result should be:
<root>
<level1>
<level2>
<level3 />
<level3 />
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="now is the time for XYZ">
<options>
<option this="that" />
<option me="you" />
<option here="now" /> // <- this one new
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="this one is not of interest">
<options>
<option this="that" />
<option me="you" />
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
<level1>
<level2>
<level3>
<level4>
<level5>
<level6 here="now is the time for ABC">
<options>
<option this="that" />
<option me="you" />
<option here="now" />
</options>
</level6>
</level5>
</level4>
</level3>
</level2>
</level1>
</root>
Assume that I can read the data out of the DB into a string, and that I know how to update the DB, so it's really how to manipulate the xml string in SQL (SQL Server).
You can use XML DML (data modification) with the .modify function to change the XML.
SET #xml.modify('
insert <option here="now" />
as last into
( /root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(/option[#here = "now"])]
)[1]');
This works as follows:
insert <option here="now" /> this is the value we are inserting
as last into it goes after other child nodes of the selected one
/root/level1/level2/level3/level4/level5/level6 this gets us that level6 node
[substring(#here, 1, 15) = "now is the time"] predicates the node to have a here attribute starting with that value. You must modify the length parameter to match the value you are comparing. There is no LIKE in XQuery
/options [not(/option[#here = "now"])] we look for an options node which has no option child which in turn has a here="now" attribute
[1] the first such node
If you need to modify multiple nodes within a single XML document, you need to run this in loop
DECLARE #i int = 20; --max nodes
WHILE #xml.exist('
/root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(option[#here = "now"])]
') = 1
BEGIN
SET #xml.modify('
insert <option here="now" /> as last into
( /root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(option[#here = "now"])]
)[1]');
SET #i -= 1;
IF #i = 0
BREAK;
END;
You can also do this for a whole table
DECLARE #i int = 20; --max nodes
WHILE EXISTS (SELECT 1
FROM YourTable
WHERE XmlColumn.exist('
/root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(option[#here = "now"])]
') = 1)
BEGIN
UPDATE t
SET XmlColumn.modify('
insert <option here="now" /> as last into
( /root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(option[#here = "now"])]
)[1]')
FROM YourTable t
WHERE XmlColumn.exist('
/root/level1/level2/level3/level4/level5/level6
[substring(#here, 1, 15) = "now is the time"]
/options [not(option[#here = "now"])]
') = 1;
SET #i -= 1;
IF #i = 0
BREAK;
END;
For very large datasets it may be faster to rebuild the whole XML using XQuery, with the extra node added using Constructed XML.
db<>fiddle

How to replace a tag with another in sql server XML file

I am working on a table in sql server which stores xml file in a column. In that xml file I am doing some changes. The XML file looks like:
<Report version=1>
<Title>
<Student>
<InputNumber type="int" min="0" max="100" name="age" description="Age
of student">
<Value>20</Value>
</InputNumber>
<InputNumber type="int" min="0" max="100" name="height"
description="height of student">
<Value>170</Value>
</InputNumber>
</Student>
</Title>
</Report>
I understand the usage of modify function for updating attributes or text present between tags as:
UPDATE student
SET dataxml.modify('replace value of (/Report/#version)[1] with "2"')
WHERE id=10
or
UPDATE student
SET dataxml.modify('replace value of (/Report/Title/Student/InputNumber[1]/Value[1]/text())[1] with "21"')
WHERE id=10
But now I want to replace entire tag with another tag i.e.
<InputNumber type="int" min="0" max="100" name="height"
description="height of student">
<Value>170</Value>
</InputNumber>
with
<InputText name="height"
description="height of student">
<Value>170 cm</Value>
</InputText>
I found something on internet like this and tried.
Update Student
set dataxml = replace(cast(dataxml as nvarchar(max)),'/Report/Title/Student/InputNumber[2]>','InputText>')
WHERE id=10
It says updated successfully. But I don't see the change in XML.
How can I do that?
First of all: Your XML is not valid. The attribute version=1 must be version="1".
Second: The verb tag is just one markup like <Student> or </Student>, but the whole node with attributes and nested sub-nodes is called node or - as a special type of node - element.
Now to your issue:
We need a declared table to simulate your issue:
DECLARE #student TABLE(ID INT IDENTITY, dataxml XML);
INSERT INTO #student VALUES
(N'<Report version="1">
<Title>
<Student>
<InputNumber type="int" min="0" max="100" name="age" description="Age of student">
<Value>20</Value>
</InputNumber>
<InputNumber type="int" min="0" max="100" name="height" description="height of student">
<Value>170</Value>
</InputNumber>
</Student>
</Title>
</Report>');
--This is the new element we want to insert (better: want to use to replace another)
DECLARE #newElement XML=
N'<InputText name="height" description="height of student">
<Value>170 cm</Value>
</InputText>';
--approach one calls `.modify()` twice:
UPDATE #student SET dataxml.modify('insert sql:variable("#newElement") after (/Report/Title/Student/InputNumber[#name="height"])[1]');
UPDATE #student SET dataxml.modify('delete (/Report/Title/Student/InputNumber[#name="height"])[1]');
SELECT * FROM #student;
--approach two uses FLWOR-XQuery
UPDATE #student SET dataxml=dataxml.query('<Report version="{/Report/#version}">
{<Title>
<Student>
{
for $elmt in /Report/Title/Student/*
return
if(local-name($elmt)="InputNumber" and $elmt[#name="height"]) then
<InputText name="height" description="height of student">
<Value>{$elmt/Value/text()} cm</Value>
</InputText>
else
$elmt
}
</Student>
</Title>}
</Report>');
Both ideas in short:
1) We insert the new element right after the one which should be replaced and remove it in a separate step.
2) We re-create the XML via XQuery by running through the inner list of nodes within <Student> and insert the new content instead of the existing node.

How to parse XML column in SQL Server 2012?

I have never used XML parsing in SQL Server, I would like to extract the fields in its own column, get the correct data.
I have a column called CustomerHeaderUncompressed in a Customer table that looks something like below, how do I extract the fields and the data in SQL Server 2012?
<CustomerHeaderData>
<CustomerHeader>
<shippingmethod Value="00000000-0000-0000-0000-000000000000" Name="" />
<discount Value="" />
<customdiscount Value="0" />
<ponumber Value="9909933793" />
<tax1 Value="-1" />
<tax2 Value="-1" />
<tax3 Value="0" />
<tax3name Value="" />
<tax4 Value="0" />
<Freight />
<ClientExtraField6 Value="5" />
<ClientExtraField7 Value="3" />
<dateneeded Value="01/01/0001 00:00:00" />
<ClientTaxCodeSource>0</ClientTaxCodeSource>
<shippingbranch />
<dropnumber Value="" />
<comment Value="" />
<shippingzone Value="" />
<salespersonID Value="704e78d4-cdbb-4963-bcc2-2c83a1d5f3fd" />
<salesperson Value="Salesrep, XYZ" />
<installation Value="False" />
<salesterms Value="18" />
<HeldItemDeliveryMethod Value="0" />
<customcontrol>
<CustomCustomerHeader CultureInfo="en-US">
<BusinessSegment>TR</BusinessSegment>
<BusinessSegmentID>1</BusinessSegmentID>
<OrderType>2</OrderType>
<MarketSegment>S3</MarketSegment>
<CustomerDeliveryDate>2010-01-21</CustomerDeliveryDate>
<BuildingPermitNumber />
<FinalWallDepth />
<PricingType>2</PricingType>
<HouseBuiltBefore1978>False</HouseBuiltBefore1978>
<AttributePricing>False</AttributePricing>
<UndeterminedAttributes>False</UndeterminedAttributes>
<EventIDStatus>VerifyFailed</EventIDStatus>
<EventIDEnabled>False</EventIDEnabled>
<CustomerDiscount>0</CustomerDiscount>
<PreparedBy />
<RequestedShipDate>01/14/2010</RequestedShipDate>
<UserTestDate>01/01/0001</UserTestDate>
</CustomCustomerHeader>
</customcontrol>
</CustomerHeader>
Basically something like this:
select from your Customer table
use CROSS APPLY and the XQuery .nodes() function to grab the XML as a "on-the-fly" pseudo table of XML fragments (table alias XT, single column aliassed as XC)
"reach" into those XML fragments and pull out the values you need, using the .value() XQuery function; use element names as such, and attributes need to be prefixed with a # sign
Try this and extend it to your needs:
SELECT
ShippingMethodValue = XC.value('(shippingmethod/#Value)[1]', 'varchar(50)'),
ShippingMethodName = XC.value('(shippingmethod/#Name)[1]', 'varchar(50)'),
DiscountValue = XC.value('(discount/#Value)[1]', 'varchar(50)'),
CustomDiscountValue = XC.value('(customdiscount/#Value)[1]', 'varchar(50)'),
PONumber= XC.value('(ponumber/#Value)[1]', 'bigint' )
FROM
Customer
CROSS APPLY
CustomerHeaderUncompressed.nodes('/CustomerHeaderData/CustomerHeader') AS XT(XC)

XPATH Get list item with ID held in sibling

Using XPath in T-SQL I am trying to get an attribute value from the options list where the Id to choose the right list item is found in the Value element.
Any help would be appreciated.
declare #myTable table (pk int primary key identity(1,1), myXML xml)
insert into #myTable values ('
<Fields>
<Field ID="1111">
<Description>How Now Brown Cow</Description>
<Value>3</Value>
<Options>
<Options>
<Option OptionContent="Select one" OptionID="-1" />
<Option OptionContent="Mars" OptionID="1" />
<Option OptionContent="Pluto" OptionID="2" />
<Option OptionContent="Saturn" OptionID="3" />
</Options>
</Options>
</Field>
<Field ID="2222">
<Description>Foo Bar</Description>
<Value>2</Value>
<Options>
<Options>
<Option OptionContent="Select one" OptionID="-1" />
<Option OptionContent="Coffee" OptionID="1" />
<Option OptionContent="Tea" OptionID="2" />
<Option OptionContent="Water" OptionID="3" />
<Option OptionContent="Juice" OptionID="4" />
<Option OptionContent="Water" OptionID="5" />
</Options>
</Options>
</Field>
</Fields>
')
select
myField.ref.value('#ID', 'smallint') as [ID]
,myField.ref.value('(./Description)[1]', 'nvarchar(10)') as [Description]
,myField.ref.value('(./Value)[1]', 'int') as [Value]
,myField.ref.value('(./Options/Options/Option[#OptionID="-1"]/#OptionContent)[1]', 'nvarchar(10)') as [SelectedDescription]
from #myTable c
cross apply c.myXML.nodes('/Fields/Field') myField(ref)
ID Description Value Actual Expected
------ ----------- ----------- ---------- --------
1111 How Now Br 3 NULL Saturn
2222 Foo Bar 2 NULL Tea
Change
(./Options/Options/Option[#OptionID="-1"]/#OptionContent)[1]
to
let $id := ./Value[1] return (./Options/Options/Option[#OptionID=$id]/#OptionContent)[1]
So your query should be
select
myField.ref.value('#ID', 'smallint') as [ID]
,myField.ref.value('(./Description)[1]', 'nvarchar(10)') as [Description]
,myField.ref.value('(./Value)[1]', 'int') as [Value]
,myField.ref.value('let $id := ./Value[1] return (./Options/Options/Option[#OptionID=$id]/#OptionContent)[1]', 'nvarchar(10)') as [SelectedDescription]
from #myTable c
cross apply c.myXML.nodes('/Fields/Field') myField(ref)

Get elements of same name from XML using XQuery in SQL server

I am having following XML
DECLARE #ruleXML XML
SET #RuleXML = '<questionnaire xmlns:xsi="http://schema1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schem2" title="Sample">
<sections>
<section id="24" title="Section Title" help="" url="">
<questions />
</section>
<section id="23" title="Information" help="" url="">
<questions />
</section>
<section id="25" title="Section Title1" help="" url="">
<questions>
<question id="3" title="Question Text">
<display-rules />
<questions />
</question>
<question id="4" title="Question Text" >
<response-set type="inline" />
<display-rules />
<questions />
</question>
</questions>
</section>
</sections>
</questionnaire>'
How to get a table with question id and title from all the question nodes regardless of their level using XQUERY in SQL server?
; with xmlnamespaces (default 'http://schem2')
select tbl.col1.value('#id', 'int')
, tbl.col1.value('#title', 'varchar(100)')
from #RuleXML.nodes('//question') tbl(col1)
Working example at SQL FIddle.

Resources