SQL Update XML Value stored in ntext colum - sql

I need to update two values within XML data that is stored in a ntext column. I have a copy of the database to test with, and I haven't been successful with the information I have found.
Here is a sample the XML that I need to update:
<?xml version="1.0" encoding="utf-16"?>
<cmnReportExportParameters xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DSNs>
<ReportDSN>
<Name>Name</Name>
<Database>DBName</Database>
<Server />
<User>user</User>
<Password>removed </Password>
<DevelopmentName>DBName</DevelopmentName>
</ReportDSN>
</DSNs>
</cmnReportExportParameters>
I need to update the "user" and "password" fields within this XML data. This is part of a legacy CMS application, and I am simply supporting the system (I'm not the developer).
I can cast the data successfully select Name, Parameters, CAST(parameters as xml) from tablename
Any guidance is appreciated.

This is far from ideal, but this is the only way I can think to do it. As your column is the wrong data type (ntext) you can't use the modify XQUERY command. Thus, you have to insert your data into a temporary table, update it, and then reinsert it. This is not going to be great for performance, but it works. As I said before, fixing the data type will make this far easier:
CREATE TABLE [sample] (YourIDCol int IDENTITY(1,1),
NotXML ntext);
INSERT INTO [sample] (NotXML)
VALUES (
N'<?xml version="1.0" encoding="utf-16"?>
<cmnReportExportParameters xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DSNs>
<ReportDSN>
<Name>Name</Name>
<Database>DBName</Database>
<Server />
<User>user</User>
<Password>removed </Password>
<DevelopmentName>DBName</DevelopmentName>
</ReportDSN>
</DSNs>
</cmnReportExportParameters>');
GO
CREATE TABLE #UpdateXML (ID int, ActualXML xml);
INSERT INTO #UpdateXML (ID,ActualXML)
SELECT YourIDCol,
TRY_CONVERT(xml,NotXML)
FROM [sample]
WHERE TRY_CONVERT(xml,NotXML) IS NOT NULL
AND YourIDCol = 1; --if you need it
UPDATE #UpdateXML
SET ActualXML.modify('replace value of (cmnReportExportParameters/DSNs/ReportDSN/User/text())[1] with ("NewUsername")');
UPDATE #UpdateXML
SET ActualXML.modify('replace value of (cmnReportExportParameters/DSNs/ReportDSN/Password/text())[1] with ("NewPassword")');
UPDATE S
SET NotXML = CONVERT(ntext,CONVERT(nvarchar(MAX),U.ActualXML)) --yuck
FROM [sample] S
JOIN #UpdateXML U ON S.YourIDCol = U.ID;
SELECT *
FROM [sample];
DROP TABLE #UpdateXML;
GO
DROP TABLE [sample];

Related

SQL Replace Typed XML Data

I'm working with third-party software that stores an XML document of parameters as a column. I'm trying to write a SQL-Server script that will replace the email address in the XML below.
<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema" i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType>
So far, the closest I've gotten is this... It runs and says rows were affected but does nothing.
update t
set XMLColumn.modify('replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Key/Value/string())[1] with "dwight#staples.com"')
After reviewing other posts and Microsoft's documentation (https://learn.microsoft.com/en-us/sql/t-sql/xml/replace-value-of-xml-dml?view=sql-server-ver15#a-replacing-values-in-an-xml-instance --Item D), it seems I'm missing something regarding the namespaces. If I understand the XML correctly, it appears that there are multiple namespaces to declare. After several attempts with no luck, my lack of XML experience has me turning here.
Any help is greatly appreciated!
Please try the following solution.
As you correctly guessed, the culprit was a default namespace.
Also, I had to adjust the XPath expression.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, XMLColumn XML);
INSERT INTO #tbl (XMLColumn) VALUES
(N'<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema"
i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType>');
-- DDL and sample data population, end
-- before
SELECT * FROM #tbl;
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/2003/10/Serialization/Arrays')
UPDATE #tbl
SET XMLColumn.modify('replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Value/text())[1] with "dwight#staples.com"');
-- after
SELECT * FROM #tbl;
You muse declare default namespace
DECLARE #XML XML = N'<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema" i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType> '
set #XML.modify('
declare default element namespace "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Value/text())[1] with "dwight#staples.com"')
SELECT #XML

How to update xml column node value with another column new value at same update query?

I want to change the value of 2 columns in one table. One column is varchar and the other is XML. First of all, I want to replace the value of the RECIPIENT column with the new value and replace the node value named as RecipientNo in the XML column with the new value of RecipientNo. How can I do these two operations in the same update function? The query below works. Secondly, DATARECORD table includes too many records. Does modify function take too much time to update the records? If so, how can I increase the performance of modify function or can you suggest another alternative solution? By the way, I cannot add index to DATARECORD table. Thanks.
Here is the sample row;
ID RECIPIENT RECORDDETAILS
1 1 <?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>'
CREATE TABLE #TEMPTABLE(
ID bigint,
RECIPIENT nvarchar(max),
RECORDDETAILS xml
)
INSERT INTO #TEMPTABLE
SELECT ID,RECIPIENT,RECORDDETAILS
FROM DATARECORD WITH (NOLOCK)
WHERE cast(RECORDDETAILS as varchar(max)) LIKE '%<Code>123</Code>%' and cast(RECORDDETAILS as varchar(max)) LIKE '%MetaTag%'
UPDATE #TEMPTABLE SET RECIPIENT = CONCAT('["queryType|1","recipientNoIDENTIFICATION|',RECIPIENT,']')
UPDATE #TEMPTABLE SET RECORDDETAILS.modify('replace value of (MetaTag/RecipientNo/text())[1] with sql:column("RECIPIENT")')
UPDATE d
SET d.RECORDDETAILS =Concat('<?xml version="1.0"?>', CAST(t.RECORDDETAILS AS VARCHAR(max))),
d.RECIPIENT = t.RECIPIENT
FROM dbo.DATARECORD as d
Join #TEMPTABLE as t
ON t.ID = d.ID
It's certainly possible to update an SQL column and an XML node in the same update statement, e.g.:
create table DataRecord (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert DataRecord values
(1, N'1', N'<?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>');
create table #TempTable (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert #TempTable
select ID, Recipient, RecordDetails
from DataRecord with (nolock)
where cast(RecordDetails as varchar(max)) like '%<Code>123</Code>%' and cast(RecordDetails as varchar(max)) like '%MetaTag%'
-- Change an SQL value and an XML node in the one update statement...
update tt set
Recipient = NewRecipient,
RecordDetails.modify('replace value of (/MetaTag/RecipientNo/text())[1] with sql:column("NewRecipient")')
from #TempTable tt
outer apply (
select NewRecipient = concat('["queryType|1","recipientNoIDENTIFICATION|', Recipient, '"]')
) Calc
select * from #TempTable
Which yields:
ID Recipient RecordDetails
1 ["queryType|1","recipientNoIDENTIFICATION|1"] <MetaTag
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>["queryType|1","recipientNoIDENTIFICATION|1"]</RecipientNo>
<Name>xyz</Name>
</MetaTag>
There are a couple of things contributing to your performance problem:
Converting XML, which SQL Server essentially stores in UTF-16 encoding, to varchar (twice) is expensive. It will also trash any Unicode characters outside your database's collation.
Performing like matches on the XML (converted to varchar) will be causing TABLE SCAN operations, converting and testing every row in your table.
Some things to consider:
Add XML Index(es) to the RecordDetails column and use something like WHERE RecordDetails.exists('/MetaTag/Code[.="123"]) to short list the rows to be updated.
Alternatively, pre-shred your RecordDetails, persist the value of /MetaTag/Code/text() in a table column (e.g.: MetaTagCode), and use something like WHERE MetaTagCode='123' in your query. Adding an index to that column will allow SQL to do a much cheaper INDEX SCAN when searching for the desired value instead of a TABLE SCAN.
Since you say you cannot add indexes you're basically going to have to tolerate TABLE SCANs and just wait it out.

Selecting XML nodes with SQL Server

My XMLData is stored under table.[column] : dbo.promotions.[PromotionDiscountData]
and the XML looks like the following when i expand it:
<ArrayOfPromotionDiscountBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PromotionDiscountBase xsi:type="OrderPromotionDiscount">
<DiscountType>Fixed</DiscountType>
<DiscountAmount>5.0000</DiscountAmount>
</PromotionDiscountBase>
</ArrayOfPromotionDiscountBase>
I would like to export a report and flatten out the xml as a column with everything else in the dbo.promotions table.
What would be the best way to get the DiscountType and DiscountAmount Out of the xml?
Thanks!
Please try the following.
SQL
-- DDL and data population, start
DECLARE #tbl TABLE (ID INT IDENTITY(1,1) PRIMARY KEY,[xmlData] XML NOT NULL);
INSERT INTO #tbl([xmlData])
VALUES
(N'<ArrayOfPromotionDiscountBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PromotionDiscountBase xsi:type="OrderPromotionDiscount">
<DiscountType>Fixed</DiscountType>
<DiscountAmount>5.0000</DiscountAmount>
</PromotionDiscountBase>
</ArrayOfPromotionDiscountBase>');
-- DDL and data population, end
SELECT ID
, col.value('(DiscountType)[1]', 'VARCHAR(30)') AS [DiscountType]
, col.value('(DiscountAmount)[1]', 'DECIMAL(10,4)') AS [DiscountAmount]
FROM #tbl AS tbl
CROSS APPLY tbl.[xmlData].nodes('/ArrayOfPromotionDiscountBase/PromotionDiscountBase') AS tab(col);

How to insert an attribute into xml data which is saved as nvarchar(max)

Let's say there is the following XML data and I want to add an attribute into salary like currency="INR":
<employee>
<salary amount="6000"/>
</employee>
If this data is stored in a column of type XML, then another attribute is being added easily just by using this code snippet:
UPDATE TABLENAME
SET COLUMNNAME.modify('insert attribute currency{"INR"} into (/employee/salary)[1]')
and if this data is stored in a column of type nvarchar(max), then the following query is not working even after casting the data as xml:
UPDATE TABLENAME
SET CAST(CAST(COLUMNNAME AS VARCHAR(MAX)) AS XML).modify('insert attribute currency{"INR"} into (/employee/salary)[1]')
So, help me to resolve second point as I have a column as nvarchar and I need to insert an attribute into saved xml data.
modify() Method works only with variable/column directly and can only used in the SET clause.
So, to solve this since you are storing your data as NVARCHAR, you have two choices:
Alter your table and add a new column with XML datatype, move the data to it from your column, and then UPDATE the data using modify()
Create/declare a table to store your data as XML and do the UPDATE.
Here is an example for what you provide:
CREATE TABLE T
(
Value NVARCHAR(MAX)
);
INSERT INTO T
SELECT N'<employee>
<salary amount="6000"/>
</employee>';
DECLARE #V XML;
SELECT #V = CAST(Value AS XML)
FROM T;
SET #V.modify('insert attribute currency{"INR"} into (/employee/salary)[1]');
UPDATE T
SET Value = CAST(#V AS NVARCHAR(MAX));
SELECT * FROM T;
Live demo

Parsing xml in SQL Server 2008 with multiple xml namespace

I want to get gid and uid from the below xml. This xml exists as a column in a SQL Server 2008 table called xmlmsg. I need a SQL query that parses the table which has an xml column with the data like below:
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://api.money.com/schema/contact" Email=""
gId="11" uId="uadgra45678" Timestamp="2013-10-17T19:19:41Z"
xsi:schemaLocation="http://api.money.com/schema/contact http://api.money.com/schema/contact/contact-1.2.xsd">
</person>
(This is really a comment because it doesn't seem to answer the question implied by the title.)
This query (from LinqPad) creates and retrieves XML data:
--Drop Table #TempXml
Create Table #TempXml(Value XML)
Insert Into #TempXml(Value)Values('<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://api.money.com/schema/contact" Email=""
gId="11" uId="uadgra45678" Timestamp="2013-10-17T19:19:41Z"
xsi:schemaLocation="http://api.money.com/schema/contact http://api.money.com/schema/contact/contact-1.2.xsd">
</person>')
Select count(*) from #TempXml;
Select * from #TempXml;
Select Value.value('declare namespace AMC="http://api.money.com/schema/contact";
/AMC:person[1]/#uId','varchar(max)')
,Value.value('declare namespace AMC="http://api.money.com/schema/contact";
/AMC:person[1]/#gId','int')
From #TempXml;
Drop Table #TempXml