How to delete an item inside an XML with a certain condition? - sql

I have an XML variable value like this in SQL. (SQL SERVER 18)
DECLARE #result NVARCHAR(55) = '123'
DECLARE #XML XML = '<NewData>
<XML>
<PONo>123</SAPPONo>
<Brand>Goldilucks</Brand>
<Color>Amber Brown</Color>
<Size>L</Size>
<OrderQty>21</OrderQty>
<FinalOrderQty>21</FinalOrderQty>
<Unit>KG</Unit>
<DeliveryDate>2021-07-09T00:00:00+08:00</DeliveryDate>
<BatchNo>GC-L1</BatchNo>
<ExpiryDate>2021-08-23T00:00:00+08:00</ExpiryDate>
</XML>
</NewData>'
I want to remove the
<BatchNo>GC-L1</BatchNo> and <ExpiryDate>2021-08-23T00:00:00+08:00</ExpiryDate>
if the <PONo> = '123'.
How can I achieve that in SQL?
I am trying this solution
SET #XML.modify('delete /NewDataSet/XML/BatchNo'[PONo = sql:variable(#result)])
SET #XML.modify('delete /NewDataSet/XML/ExpiryDate'[PONo = sql:variable(#result)])
But the said items still exist in my XML.
Thank you

Related

How to add additional XML node on top of my SQL generated XML

I have generated XML from a SQL Server FOR XML PATH statement as shown here:
USE MySQLDB
SELECT *
FROM BillTable
FOR XML PATH ('BillAdd'), ROOT ('BillAddRq')
And this is the result:
<BillAddRq>
<BillAdd>
<TxnID>2432-1071510295</TxnID>
<TimeCreated>2003-12-16T01:44:55</TimeCreated>
<TimeModified>2015-12-15T22:38:33</TimeModified>
<EditSequence>1450190313</EditSequence>
<TxnNumber>413</TxnNumber>
<VendorRef_ListID>E0000-933272656</VendorRef_ListID>
<VendorRef_FullName>Timberloft Lumber</VendorRef_FullName>
<APAccountRef_ListID>C0000-933270541</APAccountRef_ListID>
<APAccountRef_FullName>Accounts Payable</APAccountRef_FullName>
<TxnDate>2016-12-01T00:00:00</TxnDate>
<DueDate>2017-12-31T00:00:00</DueDate>
<AmountDue>80.50000</AmountDue>
<TermsRef_ListID>50000-933272659</TermsRef_ListID>
<TermsRef_FullName>1% 10 Net 30</TermsRef_FullName>
<IsPaid>0</IsPaid>
</BillAdd>
<BillAdd>
<TxnID>243A-1071510389</TxnID>
<TimeCreated>2003-12-16T01:46:29</TimeCreated>
<TimeModified>2015-12-15T22:38:33</TimeModified>
<EditSequence>1450190313</EditSequence>
<TxnNumber>414</TxnNumber>
<VendorRef_ListID>C0000-933272656</VendorRef_ListID>
<VendorRef_FullName>Perry Windows & Doors</VendorRef_FullName>
<APAccountRef_ListID>C0000-933270541</APAccountRef_ListID>
<APAccountRef_FullName>Accounts Payable</APAccountRef_FullName>
<TxnDate>2016-12-02T00:00:00</TxnDate>
<DueDate>2018-01-01T00:00:00</DueDate>
<AmountDue>50.00000</AmountDue>
<TermsRef_ListID>10000-933272658</TermsRef_ListID>
<TermsRef_FullName>Net 30</TermsRef_FullName>
<IsPaid>0</IsPaid>
</BillAdd>
</BillAddRq>
Now, I'd like to encapsulate the above with these nodes:
<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="15.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
//above generated xml//
</QBXMLMsgsRq>
</QBXML>
How will I achieve this in a SQL Query I created above?
I am new to SQL Server and XML. I am trying to generate this XML directly from my database and vice versa to make it more efficient and faster — let my SQL directly communicate with XML.
ATTEMPT 1:
USE MySQLDB;
GO
DECLARE #myDoc XML;
SET #myDoc = '<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
SET #myDoc.modify('
insert
-- instead of inserting string here.. I would like to insert here the query I made above
into (/QBXML/QBXMLMsgsRq)[1]');
SELECT #myDoc;
ATTEMPT 2:
USE MySQLDB;
GO
DECLARE #myDoc XML;
SET #myDoc = '<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
DECLARE #qry XML;
SET #qry = (SELECT * FROM BillTable FOR XML PATH ('BillAdd'), ROOT ('BillAddRq'));
-- SELECT #qry;
SET #myDoc.modify('insert #qry
into (/QBXML/QBXMLMsgsRq)[1]');
SELECT #myDoc;
There are many ways to construct your XML result, consider the following three alternatives...
Use XML.modify() to insert the BillTable XML into an XML scalar variable (which includes the ?qbxml XML processing instruction):
declare #BillTableXml xml = (
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq')
);
declare #myDoc xml = '<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="15.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
set #myDoc.modify('
insert sql:variable("#BillTableXml")
into (/QBXML/QBXMLMsgsRq)[1]
');
select #myDoc as Result;
Use a nested query to construct the entire XML result (which does not, however, include the ?qbxml XML processing instruction):
select
'stopOnError' as [QBXML/QBXMLMsgsRq/#onError],
(
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq'), type
) as [QBXML/QBXMLMsgsRq]
for xml path('');
Or use an XQuery to construct the entire XML result (which also includes the ?qbxml XML processing instruction):
select BillTableXml.query('
<?qbxml version="15.0"?>,
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
{ /BillAddRq }
</QBXMLMsgsRq>
</QBXML>
') as Result
from (
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq'), type
) Data (BillTableXml);

How to pass index value from a node in XML DML with SQL

Starting with XML DML in SQL Server, pretty fine at the moment, but I am facing this challenge. I need to iterate through some defined nodes in XML data stored in SQL Server.
Already check this as reference, it gives a clue but still I did not figure it out how to send a SQL variable as an index in XML DML Reference.
Suppose the following XML data:
<materials>
<est_mat>
<pos>20</pos>
<item>BOX</item>
<qty>0.004</qty>
</est_mat>
<est_mat>
<pos>30</pos>
<item>xxx-xxx-xxx01</item>
<qty>1</qty>
</est_mat>
<est_mat>
<pos>40</pos>
<item>xxx-xxx-xxx02</item>
<qty>1</qty>
</est_mat>
</materials>
So what I am looking is to iterate through all number of <est_mat> nodes and replace <pos> attribute starting from 10, then next node will be 20 and so on.
So far I have this :
--starting of code
declare #cnt int = 10
declare #totalchildren varchar(300)
declare #pos int = 1
--returns the number of nodes
SET #totalchildren = (SELECT (XMLData.value('count(/materials/est_mat)', 'int'))
FROM TABLE_XMLFiles
WHERE myref = 173)
WHILE #cnt < #totalchildren
BEGIN
--PRINT #cnt
UPDATE TABLE_XMLFiles
SET XMLData.modify('replace value of (/materials/est_mat/pos[sql:variable("#pos")])[[1]] with sql:variable("#cnt")')
WHERE myref = 173
SET #cnt = #cnt + 1
SET #pos = #pos + 10
END
--end of code
Error:
XQuery [BinControl_XMLFiles.XMLData.modify()]: The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content, found 'element(pos,xdt:untyped) ?'
Question is: how I can send a SQL variable as an index position like this:
SET XMLData.modify('replace value of (/materials/est_mat/pos/text())[sql:variable("#pos")]
with sql:variable("#cnt")')
as the value which I am replacing it works by sending it this way with sql:variable("#cnt") - already tried it and works but I am still not figuring it out how to send a variable through the index context.
Thanks in advance for your attention.
Why not just ignore the exsting <pos>-element and re-build the XML?
DECLARE #xml XML=
N'<materials>
<est_mat>
<pos>20</pos>
<item>BOX</item>
<qty>0.004</qty>
</est_mat>
<est_mat>
<pos>30</pos>
<item>xxx-xxx-xxx01</item>
<qty>1</qty>
</est_mat>
<est_mat>
<pos>40</pos>
<item>xxx-xxx-xxx02</item>
<qty>1</qty>
</est_mat>
</materials>';
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) * 10 AS pos
,em.value(N'item[1]',N'nvarchar(max)') AS item
,em.value(N'qty[1]',N'decimal(16,4)') AS qty
FROM #xml.nodes(N'/materials/est_mat') AS A(em)
FOR XML PATH('est_mat'),ROOT('materials')
UPDATE Your follow-up question
(Please avoid chameleon questions!)
Your structure might be queried in two combined steps. One query picks out all existing nodes, which are not called <materials> and then adds the query I stated above as a sub-element.
Hint The appropriate date format within XML is ISO8601. Your value 02092017 is culture depending and therefore something you should avoid. Better 2017-02-09 or 2017-02-09T00:00:00 (If it's not the 2nd of September :-) )
DECLARE #xml XML=
N'<order>
<orderbook>
<date> 02092017 </date>
</orderbook>
<materials>
<est_mat>
<pos>20</pos>
<item>BOX</item>
<qty>0.004</qty>
</est_mat>
<est_mat>
<pos>30</pos>
<item>xxx-xxx-xxx01</item>
<qty>1</qty>
</est_mat>
<est_mat>
<pos>40</pos>
<item>xxx-xxx-xxx02</item>
<qty>1</qty>
</est_mat>
</materials>
</order>';
SELECT #xml.query(N'/order/*[local-name()!="materials"]') AS [*]
,(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) * 10 AS pos
,em.value(N'item[1]',N'nvarchar(max)') AS item
,em.value(N'qty[1]',N'decimal(16,4)') AS qty
FROM #xml.nodes(N'order/materials/est_mat') AS A(em)
FOR XML PATH('est_mat'),ROOT('materials'),TYPE
)
FOR XML PATH(N'order');
Attention: The XML's inner order of nodes might be changed...

Traversing Two XMLs in getting intersection of data in SQL

I have two xmls like below. i want to return the first xml in which ids are matched with second xml.
DECLARE
#x xml,
#y xml
SET #x = '<roots><root><id>1</id><name>rp</name></root><root><id>2</id><name>pr</name></root><root><id>3</id><name>rrp</name></root></roots>'
SET #y = '<root><id>1</id><id>5</id><id>6</id></root>'
output: <roots><root><id>1</id><name>rp</name></root></roots>
i am able to acheive this with loop. But is there any simple way to do this.
Shred #x on roots/root and extract the XML and the value for id.
Check against #y in the where clause using exist() and sql:column().
select X.root
from (
select X.X.query('*') as root,
X.X.value('(id/text())[1]', 'int') as id
from #x.nodes('/roots/root') as X(X)
)
as X
where #y.exist('/root/id/text()[. = sql:column("X.id")]') = 1
for xml path(''), root('roots')
Result:
<roots>
<root>
<id>1</id>
<name>rp</name>
</root>
</roots>

How to get value from XML attribute using Sql:Variable in xquery

I want to get attribute value from XML using Xquery.
MY XML is
<Answers>
<AnswerSet>
<Answer questionId="NodeID">155</Answer>
<Answer questionId="ParentNode" selectedValue="12">Product</Answer>
</AnswerSet>
</Answers>
Below is my query.
DECLARE #Field Varchar(100)
DECLARE #Attribute VARCHAR(100)
SET #Field='ParentNode'
SET #Attribute = 'selectedValue'
SELECT ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'') ,
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
FROM node
WHERE id=155
below line is working fine with sql:variable
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'')
but I am getting error in below line..
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
Any ideas on how to get provided attribute(#Attribute) value in result?
Try something like
ISNULL(#Xml.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/#*[local-name() = sql:variable("#Attribute")])[1]','varchar(max)'),'')

Updating XML attribute in SQL Server XML column

I am trying to update a node of in my XML which is stored in a SQL Server XML column, the line below works if my XML is in a XML element but now I somehow need to change it to XML attributes, apparently the line becomes invalid after the change.
Works for XMLElement:
UPDATE [Customers]
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher/Qty/text())[1] with "50"')
WHERE voucherXML.value('(/ArrayOfCampaignVoucher/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
I tried changing the statement like this but no luck, no errors but QTY values doesn't get change to the value of #NewQuantity:
UPDATE [Customers]
SET voucherXML='<ArrayOfCampaignVoucher xmlns:xsd="http://www.w3.org/2001/XMLSchema" Qty="' + CAST(#NewQuantity AS NVARCHAR(16)) + '" />'
WHERE voucherXML.value('(/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
This is how my XML looks in the SQL Server XML column:
<ArrayOfCampaignVoucher xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CampaignVoucher VouCode="Vouc001" Qty="16" />
<CampaignVoucher VouCode="Vouc002" Qty="18" />
<CampaignVoucher xsi:nil="true" />
</ArrayOfCampaignVoucher>
You should use the XQuery functions - not string together your XML like this....
Try this instead:
DECLARE #newquantity INT = 55
UPDATE dbo.Customers
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher[#VouCode="Vouc002"]/#Qty)[1] with sql:variable("#NewQuantity") ')
This works for me - the Qty attribute of the node with VouCode="Vouc002" gets updated to the value I defined before in a SQL variable
Update: to insert a new <CampaignVoucher> entry, use XQuery something like this:
UPDATE dbo.Customers
SET voucherXML.modify('insert <CampaignVoucher VouCode="Vouc003" Qty="42" />
as last into (/ArrayOfCampaignVoucher)[1]')