SQL Server XQuery.modify syntax issues, unable to resolve - sql

I have a SQL Server 2016 table dbo.Agent which has a column called XMLDta, but its datatype is nvarchar(max).
The data within this column is structured as XML, like so:
<misc id="m12345">
<pauth id="p12345">
<AEmail/>
<ipauth id="i12345">
<IProd>xxxxxx</IProd>
<achannel id="00000000">
<Chan>ABCEDF</Chan>
<Selected>1</Selected>
</achannel>
<Seg>ZZZ</Seg>
<states id="s12345">
<Sel>0</Sel>
<Avail>1</Avail>
<State>XX</State>
</states>
<states id="s67890">
<Sel>1</Sel>
<Avail>1</Avail>
<State>YY</State>
</states>
<LOB>FFFF</LOB>
<AUW>abc#email.com</AUW>
<WQue>10</WQue>
<AgChan>ABCEDF,</AgChan>
<State>XX,YY,</State>
<Status>Active</Status>
</ipauth>
<ipauth>
....
</ipauth>
</pauth>
</misc>
Trying to modify the Status node in above xml using following XQuery.modify() SQL/XQuery statement:
UPDATE dbo.Agent
SET CAST(xmldta AS xml).modify('replace value of (/misc/pauth/ipauth/Status/text()) with "Pending"')
WHERE agID = 209
But I keep getting the following error:
Incorrect syntax near '('.
Also tried using this SQL:
DECLARE #Dta As XML
DECLARE #id AS INT = 209
SELECT #Dta = CAST(XMLDta AS XML)
FROM dbo.Agent
WHERE agID = #id
UPDATE dbo.Agent
SET #Dta.modify('replace value of (/misc/pauth/ipauth/Status/text()) with "Pending"')
WHERE agID = 209
But I still get the same error:
Incorrect syntax near '('.
How do I correctly structure my SQL statement so it can reference the XMLDta column within the XQuery.modify function?
Please remember XMLDta column has a datatype of nvarchar(max) in the dbo.Agent table.
If I am going about this wrong way, please let me know and suggest better path.
Thanks and any guidance would be greatly appreciated.
Rebecca

Please take into account that the SQL Server XQuery .modify() method will update just one single XML element at a time.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata NVARCHAR(MAX));
INSERT INTO #tbl (xmldata) VALUES
(N'<misc id="m12345">
<pauth id="p12345">
<AEmail/>
<ipauth id="i12345">
<IProd>xxxxxx</IProd>
<achannel id="00000000">
<Chan>ABCEDF</Chan>
<Selected>1</Selected>
</achannel>
<Seg>ZZZ</Seg>
<states id="s12345">
<Sel>0</Sel>
<Avail>1</Avail>
<State>XX</State>
</states>
<states id="s67890">
<Sel>1</Sel>
<Avail>1</Avail>
<State>YY</State>
</states>
<LOB>FFFF</LOB>
<AUW>abc#email.com</AUW>
<WQue>10</WQue>
<AgChan>ABCEDF,</AgChan>
<State>XX,YY,</State>
<Status>Active</Status>
</ipauth>
<ipauth>....</ipauth>
</pauth>
</misc>');
-- DDL and sample data population, end
DECLARE #Dta AS XML
, #param VARCHAR(30) = 'Pending'
, #id AS INT = 1;
SET #Dta = (SELECT TRY_CAST(xmldata AS XML) FROM #tbl WHERE ID = #id);
SET #Dta.modify('replace value of (/misc/pauth/ipauth/Status/text())[1] with sql:variable("#param")');
UPDATE #tbl
SET xmldata = TRY_CAST(#Dta AS NVARCHAR(MAX))
WHERE ID = 1;
SELECT * FROM #tbl;

Related

Update multiple XML nodes in single Update SQL statement

I want to update multiple XML nodes in single Update query
XML:
<TransmissionData>
<HolderName>Tony Chase</CardHolderName>
<Type>VS</CardType>
<TransactionDetails>
<TransactionId />
</TransactionDetails>
<ValueType>CAPT</ValueType>
</TransmissionData>
This is what I have tried:
DECLARE #Type nVarchar(10) = 'MS'
DECLARE #ValueType nVarchar(10) = 'OPT'
DECLARE #TransactionId bigint = 122344555
UPDATE
Table1
SET
Data.modify('replace value of (/TransmissionData/CardType/text())[1] with sql:variable("#Type")'),
Data.modify('replace value of (/TransmissionData/ValueType/text())[2] with sql:variable("#ValueType")'),
Data.modify('replace value of (/TransmissionData/TransactionDetails/TransactionId/text())[1] with sql:variable("#TransactionId")')
WHERE
RequestId = 2133831593
It works only for single Update, if I use more then one like ValueType and TransactionId, it shows an error. Please help me - how to update this?
Msg 264, Level 16, State 1, Line 7
The column name 'TransmissionData' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
Please try the following solution.
It is using XQuery and its FLWOR expression.
It will update XML elements in question, leaving everything else intact.
I had to fix the input XML to make it well-formed.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<TransmissionData>
<CardHolderName>Tony Chase</CardHolderName>
<CardType>VS</CardType>
<TransactionDetails>
<TransactionId />
</TransactionDetails>
<ValueType>CAPT</ValueType>
</TransmissionData>');
-- DDL and sample data population, end
DECLARE #Type NVARCHAR(10) = 'MS'
, #ValueType NVARCHAR(10) = 'OPT'
, #TransactionId BIGINT = 122344555;
UPDATE #tbl
SET xmldata = xmldata.query('<TransmissionData>
{
for $x in /TransmissionData/*
return if (local-name($x)="CardType") then
<CardType>{sql:variable("#Type")}</CardType>
else if (local-name($x)="ValueType") then
<ValueType>{sql:variable("#ValueType")}</ValueType>
else if (local-name($x)="TransactionDetails") then
<TransactionDetails>
<TransactionId>{sql:variable("#TransactionId")}</TransactionId>
</TransactionDetails>
else $x
}
</TransmissionData>');
-- test
SELECT * FROM #tbl;

How to not insert xmlns in SQL Server?

I need to add some XML elements into an XML column in SQL Server.
Here's a simplified example of my code:
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) + '</Rate>'
UPDATE [TableName]
SET [XMLValue].modify('declare namespace ns="http://www.mycompany.com/schema";insert sql:variable("#temp") as last into (/ns:Element1/ns:Element2)[1]')
WHERE id = #Id
Here's the output:
<Rate xmlns="">12.00</Rate>
So, the code is working, however, how do I remove the xmlns="" attribute?
Why are you inserting a namespace if you don't want one in the xml?
DECLARE #RateAmt decimal(9,2) = 12.00
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) + '</Rate>'
DECLARE #tempTable TABLE
(
Column1 Xml
)
INSERT INTO #tempTable(Column1)
SELECT #temp
OR
UPDATE #tempTable
SET Column1 = (SELECT #temp)
SELECT * FROM #tempTable
<Rate>12.00</Rate>
(1 row(s) affected)
There is an accepted answer already (especially concerning your namespace issue), great, just some hints:
There are very rare situation where one should create XML via string concatenation... Especially in connection with strings (special characters!), numbers (format!) and date/time values (culture and format!) it is much better to rely on the implicit translations using SELECT ... FOR XML ...
DECLARE #RateAmt DECIMAL(12,4)=12.0;
This is possible, but not good:
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) +'</Rate>'
Better try this
DECLARE #temp XML=(SELECT #RateAmt FOR XML PATH('Rate'));
Your attempt to insert this into existing XML can be done the way you do it already (create the XML-node externally and insert it as-is), it might be easier to insert the plain value:
DECLARE #tbl TABLE(ID INT IDENTITY,XMLValue XML);
INSERT INTO #tbl VALUES
(N'<Element1><Element2><test>FirstTest</test></Element2></Element1>')
,(N'<Element1><Element2><test>Second</test></Element2></Element1>');
--ID=1: Insert the way you do it:
UPDATE #tbl
SET [XMLValue].modify('insert sql:variable("#temp") as last into (/Element1/Element2)[1]')
WHERE id = 1
--ID=2: Insert the value of #RateAmt directly
SET #RateAmt=100.00;
UPDATE #tbl
SET [XMLValue].modify('insert <Rate>{sql:variable("#RateAmt")}</Rate> as last into (/Element1/Element2)[1]')
WHERE id = 2
This is Result ID=1
<Element1>
<Element2>
<test>FirstTest</test>
<Rate>12.0000</Rate>
</Element2>
</Element1>
And ID=2
<Element1>
<Element2>
<test>Second</test>
<Rate>100</Rate>
</Element2>
</Element1>

Easiest way to query a SQL Server 2008 R2 XML data type?

I need to get a node value in an XML data type column.
<CustomContentData>
<prpIsRSSFeed>false</prpIsRSSFeed>
</CustomContentData>
How is this done in SQL Server?
The column name is ClassXML
Use XQuery, a simple example with your data would be:
DECLARE #T TABLE (ClassXML XML);
INSERT #T (ClassXML)
VALUES ('<CustomContentData>
<prpIsRSSFeed>false</prpIsRSSFeed>
</CustomContentData>');
SELECT t.ClassXML.value('CustomContentData[1]/prpIsRSSFeed[1]', 'VARCHAR(5)')
FROM #T AS t;
If the column is already XML data type in SQL Server, then the code below should work by using the value function with XPATH. If it's stored as a varchar, you'd just need to replace ClassXML.value with CONVERT(XML, ClassXML).value. Hope this helps!
DECLARE #Data TABLE (ClassXML XML)
INSERT #Data VALUES ('<CustomContentData><prpIsRSSFeed>false</prpIsRSSFeed></CustomContentData>')
SELECT
CONVERT(BIT, CASE WHEN ClassXML.value ('(/CustomContentData/prpIsRSSFeed)[1]',
'VARCHAR(50)') = 'true' THEN 1 ELSE 0 END) AS IsRssFeed
FROM #Data
Yields output
IsRssFeed
---------
0

How to convert T-SQL that deals with OPENXML, WITH and BIGINT?

I'm trying to convert a procedure to use in SQL Azure. I first got an error on OPENXML saying it's not supported on SQL Azure, then I find out it can be replaced with nodes.
But I'm not sure how to convert the WITH (Id BIGINT '.') part. I know WITH creates a subquery but what is the '.' doing here?
CREATE Procedure [dbo].[DocsR]
#ids xml -- <Ids><Id>1</Id><Id>2</Id></Ids>
AS
BEGIN
SET NOCOUNT ON;
DECLARE #IdsXml xml
exec sp_xml_preparedocument #IdsXml OUTPUT, #Ids
SELECT
DoctId,
DocNm
FROM
Docs
WHERE
--DocId IN (SELECT Id FROM OPENXML(#IdsXml, '/Ids/Id', 2) WITH (Id BIGINT '.'))
DocId IN (SELECT Id FROM #IdsXml.nodes('/Ids/Id') WITH (Id BIGINT '.'))
END
GO
Error:
Incorrect syntax near the keyword 'with'. If this statement is a common
table expression, an xmlnamespaces clause or a change tracking context clause,
the previous statement must be terminated with a semicolon.
Try the following:
SELECT
DoctId,
DocNm
FROM
Docs
WHERE
DocId IN (SELECT Id = node.value('.', 'INT')
FROM #IdsXml.nodes('/Ids/Id') AS R(node))

how to get values inside an xml column, when it's of type nvarchar

My question is similar to this one: Choose a XML node in SQL Server based on max value of a child element
except that my column is NOT of type XML, it's of type nvarchar(max).
I want to extract the XML node values from a column that looks like this:
<Data>
<el1>1234</el1>
<el2>Something</el2>
</Data>
How can I extract the values '1234' and 'Something' ?
doing a convert and using the col.nodes is not working.
CONVERT(XML, table1.col1).value('(/Data/el1)[1]','int') as 'xcol1',
After that, I would like to do a compare value of el1 (1234) with another column, and update update el1 as is. Right now I'm trying to just rebuild the XML when passing the update:
ie
Update table set col1 ='<Data><el1>'+#col2+'</el1><el2>???</el2>
You've got to tell SQL Server the number of the node you're after, like:
(/Data/el1)[1]
^^^
Full example:
declare #t table (id int, col1 varchar(max))
insert #t values (1, '<Data><el1>1234</el1><el2>Something</el2></Data>')
select CAST(col1 as xml).value('(/Data/el1)[1]', 'int')
from #t
-->
1234
SQL Server provides a modify function to change XML columns. But I think you can only use it on columns with the xml type. Here's an example:
declare #q table (id int, col1 xml)
insert #q values (1, '<Data><el1>1234</el1><el2>Something</el2></Data>')
update #q
set col1.modify('replace value of (/Data/el1/text())[1] with "5678"')
select *
from #q
-->
<Data><el1>5678</el1><el2>Something</el2></Data>
At the end of the day, SQL Server's XML support makes simple things very hard. If you value maintainability, you're better off processing XML on the client side.