xml nodes() in tsql - matching parent nodes with child node attributes - sql-server

I am trying to query following xml structure:
<effect><![CDATA[<p>some text</p>]]>
<product code="4298271" />
<product code="4298273" />
<product code="4298274" />
<product code="4298275" />
<product code="4298276" />
</effect>
<effect><![CDATA[<p>some other text</p>]]>
<product code="5298271" />
<product code="5298273" />
<product code="5298274" />
<product code="5298275" />
<product code="5298276" />
</effect>
I need to transform this data to a table like below:
Effect ProductCode
some text 4298271
some text 4298273
some text 4298274
...
...
Is it even possible ? I can get the list of product rows which are under every effect parent but no idea how to match it with the effect header from which the text should be queried.
Let's say that effect is root node for simplifying.

Even though the XML isn't really a valid document - you can still query it in T-SQL using XQuery - something like this:
DECLARE #input XML = '<effect><![CDATA[<p>some text</p>]]>
<product code="4298271" />
<product code="4298273" />
<product code="4298274" />
<product code="4298275" />
<product code="4298276" />
</effect>
<effect><![CDATA[<p>some other text</p>]]>
<product code="5298271" />
<product code="5298273" />
<product code="5298274" />
<product code="5298275" />
<product code="5298276" />
</effect>'
SELECT
xc.value('(../text())[1]', 'varchar(50)'),
xc.value('(#code)[1]', 'int')
FROM
#input.nodes('/effect/product') AS XT(XC)
This produces an output:
Adjust as needed ....

Related

Update XML stored in a XML column in SQL Server Update

Set Url value by applying filter based on location-type value from given sample.
<page id="{Page_ID}" name="" type="dat" size="0" sequence="26" originalpagenumber="26" location_type="3" location="">
<content description="" content_format="26" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
The XML you've shared is invalid so I've taken the liberty to make it valid in an effort to show you how I might approach this.
I've made the assumption you're looking to update values in a table somewhere in your environment. You can run this example in SSMS.
THIS IS MEANT AS AN EXAMPLE ONLY. DO NOT BLINDLY RUN THIS AGAINST PRODUCTION DATA.
First, I created a mock-up table that holds some page XML data. I assumed that a single XML row could contain multiple page nodes with duplicate location_type values.
DECLARE #Pages TABLE ( page_xml XML );
INSERT INTO #Pages ( page_xml ) VALUES (
'<pages>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="26" originalpagenumber="26" location_type="3" location="">
<content description="" content_format="26" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="27" originalpagenumber="27" location_type="3" location="">
<content description="" content_format="27" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="28" originalpagenumber="28" location_type="8" location="">
<content description="" content_format="28" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
</pages>' );
Selecting the current results from #Pages shows the following XML:
<pages>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="26" originalpagenumber="26" location_type="3" location="">
<content description="" content_format="26" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="27" originalpagenumber="27" location_type="3" location="">
<content description="" content_format="27" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="28" originalpagenumber="28" location_type="8" location="">
<content description="" content_format="28" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
</pages>
There are two pages with a location_type of 3, and one with a location_type of 8.
Next, I declared a few variables which I then used to modify the XML.
DECLARE #type INT = 3, #url VARCHAR(255) = 'http://www.google.com';
/* Update each Url text for the specified location_type */
WHILE EXISTS ( SELECT * FROM #Pages WHERE page_xml.exist( '//pages/page[#location_type=sql:variable("#type")]/AdditionalInfo/Url[text()!=sql:variable("#url")]' ) = 1 )
UPDATE #Pages
SET
page_xml.modify( '
replace value of (//pages/page[#location_type=sql:variable("#type")]/AdditionalInfo/Url/text()[.!=sql:variable("#url")])[1]
with sql:variable("#url")
' );
After running the update the XML now contains:
<pages>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="26" originalpagenumber="26" location_type="3" location="">
<content description="" content_format="26" />
<AdditionalInfo>
<Url>http://www.google.com</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="27" originalpagenumber="27" location_type="3" location="">
<content description="" content_format="27" />
<AdditionalInfo>
<Url>http://www.google.com</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
<page id="{Page_ID}" name="" type="dat" size="0" sequence="28" originalpagenumber="28" location_type="8" location="">
<content description="" content_format="28" />
<AdditionalInfo>
<Url>https://s3-eu-west-1/2019/may//001063/gvb140/683c82a3-b3f5-49ee-a34e-01859e8e2228.mp3</Url>
<Name />
<Encoding>audio</Encoding>
<SecurityType>IBCloud</SecurityType>
</AdditionalInfo>
</page>
</pages>
Using the WHILE EXISTS (... ensures that all Url nodes for the requested location_type are updated. In my example here there are two pages with a value of 3 that are updated, while location_type 8 is left alone.
Basically, what this is doing is updating the Url for any page with the requested location_type to the new #url value.
There are two key pieces here, the first being:
.exist( '//pages/page[#location_type=sql:variable("#type")]/AdditionalInfo/Url[text()!=sql:variable("#url")]' )
Which looks for the requested location_type that doesn't have the new #url value.
And the second:
page_xml.modify( '
replace value of (//pages/page[#location_type=sql:variable("#type")]/AdditionalInfo/Url/text()[.!=sql:variable("#url")])[1]
with sql:variable("#url")
' );
Which modifies (updates) the Url for the location type that has yet to be updated. The two "conditions" allow for a loop (WHILE) that ends when they're no longer met, ensuring that all page nodes for the requested location_type are updated.

SQL Server xml query doesn't return expected result

I have a column in my database FlowDetailParameter with XML Type .My table has one column FlowDetailParameter and 3 rows with these data :
row 1
<ArrayOfFlowDetailParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FlowDetailParameters>
<DepartmentId>7</DepartmentId>
<UserId>6</UserId>
<Username>4</Username>
<FullName>کارشناس معاینه فنی</FullName>
<ConfirmDateTime>2018-11-01T10:45:29.7371421+03:30</ConfirmDateTime>
<Comment>اولین IP تاییدی</Comment>
<Status>Accept</Status>
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>3</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
</ArrayOfFlowDetailParameters>
row 2
<ArrayOfFlowDetailParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FlowDetailParameters>
<DepartmentId>7</DepartmentId>
<UserId>6</UserId>
<Username>4</Username>
<FullName>کارشناس معاینه فنی</FullName>
<ConfirmDateTime>2018-11-01T10:45:40.437481+03:30</ConfirmDateTime>
<Comment>دومین IP تاییدی</Comment>
<Status>Accept</Status>
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>3</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
</ArrayOfFlowDetailParameters>
row 3
<ArrayOfFlowDetailParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FlowDetailParameters>
<DepartmentId>7</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>3</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status />
<AttachmentId />
</FlowDetailParameters>
</ArrayOfFlowDetailParameters>
I want to find the departmentId=3 and status=Pending ,so the expected result should return 2 rows .So here is my query :
select Requests.* from Requests
where
((SELECT count(*)
FROM Requests t
CROSS APPLY t.FlowDetailParameter.nodes ('/ArrayOfFlowDetailParameters/FlowDetailParameters') x(v)
where x.v.value('(DepartmentId/text())[1]', 'bigint')=3 and x.v.value('(Status/text())[1]', 'varchar(50)') = 'Pending') >0)
But my query returns all rows (3 rows) why ?
First to answer your question "why?":
Your sub-query is not a correlated sub-query. There is no connection to the current row from the outer SELECT. So - assuming there is at least 1 row fulfilling your condition - this will always provide a count>0.
Although your approach can be corrected, I'd suggest to use the XML-method .exist() and provide the filter as XPath/XQuery:
SELECT *
FROM Requests r
WHERE r.FlowDetailParameter.exist(N'/ArrayOfFlowDetailParameters
/FlowDetailParameters[(DepartmentId/text())[1]=3
and (Status/text())[1]="Pending"]')=1;
This will check for the existance of any <FlowDetailParameters> for the given condition.
If you want to introduce the filter dynamically, you can use sql:variable() or sql:column() instead of 3 and "Pending"
DECLARE #depId INT=3;
DECLARE #status VARCHAR(100)='Pending';
SELECT *
FROM Requests r
WHERE r.FlowDetailParameter.exist(N'/ArrayOfFlowDetailParameters
/FlowDetailParameters[(DepartmentId/text())[1]=sql:variable("#depId")
and (Status/text())[1]=sql:variable("#status")]')=1

SQL Server xml query can't return expected result

I have this xml data in my table as you can see :
<ArrayOfFlowDetailParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FlowDetailParameters>
<DepartmentId>23</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>22</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>7</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
<FlowDetailParameters>
<DepartmentId>18</DepartmentId>
<UserId xsi:nil="true" />
<Username />
<FullName />
<ConfirmDateTime xsi:nil="true" />
<Comment />
<Status>Pending</Status>
<AttachmentId />
</FlowDetailParameters>
</ArrayOfFlowDetailParameters>
when i want to find the departmentid with value=22 my query returns 0 result but when i search value=23 it returns 1 result i think it is because of [1].
declare #departmentId nvarchar(max)
set #departmentId=22
select Requests.* from Requests
where
and (FlowDetailParameter.value('(/ArrayOfFlowDetailParameters/FlowDetailParameters/DepartmentId/text())[1]','bigint') = #departmentId )
It sounds like you're trying to query a list of requests where the FlowDetailParameter column's XML contains a record where the DepartmentId matches your #departmentId variable, right?
The [1] in your query specifies to only check the first occurrence of DepartmentId in each row's FlowDetailParameter XML. You won't get a match unless the first DepartmentId in the XML matches your parameter.
Instead, you can use the following query to find all requests that have a FlowDetailParameter matching the #departmentid variable using the nodes() method.
SELECT r.*
FROM #Requests r
WHERE EXISTS (SELECT *
FROM r.FlowDetailParameter.nodes('/ArrayOfFlowDetailParameters/FlowDetailParameters/DepartmentId') as Parms(DepartmentId)
WHERE DepartmentId.value('.', 'bigint') = #departmentid)

Updating multiple XML columns using single update in SQL Server

I would like to update a table_A with new values for (AcresDist1, txtAcresDist1, txtAcresDist1Total) XML columns from a second table_B.
The column P_XML is of XML type. I know how to update a single column at once, but I would like to know how to update multiple columns in the XML using a single update statement. Thanks
SQL code:
UPDATE S
SET
P_XML.modify( N'replace value of (/FormValue/f1152_F1/Field[(id/text())[1]="AcresDist1"]/value/text())[1] with sql:column(''T.AcresDist1'')' )
, P_XML.modify( N'replace value of (/FormValue/f1152_F1/Field[(id/text())[1]="txtAcresDist1"]/value/text())[1] with sql:column(''T.txtAcresDist'')' )
, P_XML.modify( N'replace value of (/FormValue/f1152_F1/Field[(id/text())[1]="txtAcresDist1Total"]/value/text())[1] with sql:column(''T.txtAcresDist1Total'')' )
FROM
Table_A AS S
INNER JOIN
Table_B AS T ON s.P_NO = t.P_Number
AND s.FAC_RID = t.Fac_RID;
Here is the sample xml as requested. Thank you.
<FormValue>
<f1152>
<field>
<id>f1152_MainForm</id>
<value />
<tag />
<visible>true</visible>
<history>|09/28/2017 10:50:26 AM||</history>
<description />
<comment />
</field>
<field>
<id>txt_rdoCoverage</id>
<value>Development</value>
<tag />
<visible>false</visible>
<history>|09/28/2017 10:50:26 AM||</history>
<description />
<comment />
</field>
</f1152>
<f1152_F1>
<field>
<id>txtAcresDist1</id>
<value>1.2</value>
<tag />
<visible>false</visible>
<history>|09/28/2017 3:08:14 AM||</history>
<description />
<comment />
</field>
<field>
<id>txtAcresDist1Total</id>
<value>200</value>
<tag />
<visible>false</visible>
<history>|09/28/2017 3:08:14 AM||</history>
<description />
<comment />
</field>
</f1152_F1>

Empty results when filtering based on position in XQuery

I tried to print the details of 3rd and 4th products in the catalog, but I am getting empty results.
XML input:
<catalog>
<product dept="WMN">
<number>557</number>
<name language="en">Fleece Pullover</name>
<colorChoices>navy black</colorChoices>
</product>
<product dept="ACC">
<number>563</number>
<name language="en">Floppy Sun Hat</name>
</product>
<product dept="ACC">
<number>443</number>
<name language="en">Deluxe Travel Bag</name>
</product>
<product dept="MEN">
<number>784</number>
<name language="en">Cotton Dress Shirt</name>
<colorChoices>white gray</colorChoices>
<desc>Our <i>favorite</i> shirt!</desc>
</product>
</catalog>
what I tried:
SELECT xDoc.query(' for $prod in //product
let $x:=$prod
return (<Item>{data($x[3])}{data($x[4])}</Item>)')
FROM AdminDocs
where id=6
Output displayed:
<Item />
<Item />
<Item />
<Item />
This defines a loop over all products:
for $prod in //product
For each of those products, you return the third and fourth node out of a sequence of a single product, which obviously will yield empty nodes:
let $x:=$prod
return (<Item>{data($x[3])}{data($x[4])}</Item>)
For a better understanding, return the whole product node each time:
for $prod in //product
return <item>{ $prod }</item>
Which will result in something like this:
<item>
<product dept="WMN">
<number>557</number>
<name language="en">Fleece Pullover</name>
<colorChoices>navy black</colorChoices>
</product>
</item>
<item>
<product dept="ACC">
<number>563</number>
<name language="en">Floppy Sun Hat</name>
</product>
</item>
<item>
<product dept="ACC">
<number>443</number>
<name language="en">Deluxe Travel Bag</name>
</product>
</item>
<item>
<product dept="MEN">
<number>784</number>
<name language="en">Cotton Dress Shirt</name>
<colorChoices>white gray</colorChoices>
<desc>Our <i>favorite</i> shirt!</desc>
</product>
</item>
And each of those <product/> elements being a $prod[1]. Try:
for $prod in //product
return <item>{ $prod[1] }</item>
which will return the exactly same result, compared to
for $prod in //product
return <item>{ $prod[2] }</item>
(or any other positional predicate) resulting in empty <item/> nodes.
Finally, to return only the third and fourth products, shift the positional predicate into the for loop argument:
for $prod in //product[position() = 3 or position() = 4]
return <item>{ $prod }</item>
(and apply data(...) as needed, but I'd guess it won't yield the results you expect here).

Resources