SQL Replace Typed XML Data - sql

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

Related

SQL XML contains() with fn:lowercase()

I'm trying to make this Xpath query using the lowercase text() value in my contains() statement. For example, searching "New York" versus "New york" will return different results.
I plan to make sure the parameter is lowercase going into the stored procedure from now on, but I need to make sure the text() in the XML is also lowercase. I've tried a few different ways but keep getting syntax errors. Note: I'm searching the <Company> node for New York to ensure I don't get any records that match the <City> node. I had started with regular full-text contains() but have since gone to XPath for accuracy.
DECLARE #Company nvarchar(100) = "new york"
SELECT ...
FROM OrderObject o
WHERE o.Address.exist('//Company/text()[contains(.,sql:variable("#Company"))]') = 1)
XML is like this... shorted for brevity
<Address>
<Company>1</Company>
<City>2</City>
<State>3</State>
</Address>
Thanks
Here is a correct way to it.
You need to apply lower-case() function for both parameters of the contains() function.
This way stored procedure parameter could be in absolutely any case: upper, lower, mixed, etc.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<Address>
<Company>1</Company>
<City>NeW YoRk</City>
<State>NY</State>
</Address>'),
(N'<Address>
<Company>2</Company>
<City>Miami</City>
<State>FL</State>
</Address>');
-- DDL and sample data population, end
DECLARE #City NVARCHAR(100) = 'new york';
SELECT *
FROM #tbl
WHERE xmldata.exist('/Address/City[contains(lower-case((./text())[1]),lower-case(sql:variable("#City")))]') = 1;

Query to XML Node is giving me NULL Value

Trying to achieve what stored procedure used for each report in Report Server.
INSERT INTO #ReportRawXML
SELECT
ItemID, RPT.[Name] AS ReportName,
CONVERT(XML, CONVERT(VARBINARY(MAX), RPT.Content)) AS XMLdata
FROM
ReportServer.dbo.[Catalog] AS RPT
WHERE
RPT.[Name] LIKE '%catalog%'
XML sample:
<Report>
<DataSets>
<DataSet Name="DSET_ReportRepository">
<Query>
<DataSourceName>CCA_PROD</DataSourceName>
</Query>
</DataSets>
</Report>
I have a table with a XML column which I want to query, but I'm getting NULL back; I tried all possible ways, please someone advice.
SELECT
b.ItemID, b.ReportName,
n.c.value('#DataSourceName', 'VARCHAR(MAX)') AS id,
n.c.value('/DataSourceName[1]', 'VARCHAR(500)') AS DataSourceName,
n.c.value('/CommandType[1]', 'VARCHAR(100)') AS CommandType,
n.c.value('/CommandText[1]', 'VARCHAR(100)') AS CommandText
FROM
#ReportRawXML b
OUTER APPLY
b.XMLdata.nodes('/Report/DataSets/DataSet/Query') AS n(c)
Question: getting NULL in column 3 above
Some hints for your next question:
Please try to add a MCVE (a stand-alone-sample as I provide it here for you) right from the start. You know all your details, but we don't...
Try to add a sample, where the sample includes everything (e.g. your sample does not show any CommandType or CommandText
Please read about the internal formatting tools on SO how to add code, normal text, how to highlight or cite...
Please run your sample yourself. Doing so, you would have found, that the XML provided is not well-formed (missing </DataSet>).
But now to your question:
DECLARE #mockupTable TABLE(ID INT IDENTITY, XMLdata XML);
INSERT INTO #mockupTable VALUES
(N'<Report>
<DataSets>
<DataSet Name="DSET_ReportRepository">
<Query>
<DataSourceName>CCA_PROD</DataSourceName>
</Query>
</DataSet>
</DataSets>
</Report>');
--The query
SELECT b.ID
,ds.value('(Query/DataSourceName/text())[1]', 'varchar(max)') as id
,ds.value('#Name', 'varchar(max)') as id
FROM #mockupTable b
OUTER APPLY b.XMLdata.nodes('/Report/DataSets/DataSet') as n(ds);
Reading from XML you must know, that the # before the name indicates an attribute. To read the DataSet's Name attribute, you need this, but not before DataSourceName as in your own attempt.

Xquery to return rows with restricted nodes

I have a table where a column contains XML data. Now i want to retrieve those xml data with restriction of nodes. Kindly see the following example for more explanation on my scenario,
declare #table table (id int, xmlfield xml) insert into #table select 1,'<Root xmlns="">
<Sample>
<Issue>
<Level>one</Level>
<Descp>First Example</Descp>
</Issue>
<Issue>
<Level>two</Level>
<Descp>Second Example</Descp>
</Issue>
</Sample> </Root>'
select * from #table
Now i need the following result set
Id XMLfield
1 first example
ie, for the selected level,i need the decription for it. More clearly, the node should be restricted for <level>one</level>
(need: What is the description for level one ?)
thanks in advance
Have a look at the xml Data Type Methods
select id,
xmlfield.value('(//Issue[Level = "one"]/Descp/text())[1]', 'varchar(100)') as XMLField
from #table
The XQuery you're looking for is
//Issue[Level = "one"]/Descp/data()

T-SQL XML get a value from a node problem?

I have an XML like:
<?xml version="1.0" encoding="utf-16"?>
<ExportProjectDetailsMessage xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Project">
<CPProjectId>7665699f-6772-424c-8b7b-405b9220a8e7</CPProjectId>
</ExportProjectDetailsMessage>
I'm trying to get the CPProjectId as a Uniqueidentifier using:
DECLARE #myDoc xml
DECLARE #ProdID varchar(max)
SET #myDoc = '<ExportProjectDetailsMessage xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Project"><CPProjectId>7665699f-6772-424c-8b7b-405b9220a8e7</CPProjectId></ExportProjectDetailsMessage>'
SET #ProdID = #myDoc.value('(ExportProjectDetailsMessage/CPProjectId)[1]', 'varchar(max)' )
SELECT #ProdID
All i can receive is NULL =/
I've tried many combinations on #myDoc.value but no results =/
How can i retrieve the value from my XML ?
Thanks!
--EDIT:
Something that i noted, when i remove the namespace declaration from the XML it works fine!
The problem is that i need this namespaces! =/
You're right the namespace is the issue. You're query is looking for a node ExportProjectDetailsMessage but such a node doesn't exist in your document, because there is a namespace declared as a default in your document. Since you can't remove that (nor should you) you should include it in your XPATH query like so:
set #ProdId = #myDoc.value('
declare namespace PD="http://schemas.datacontract.org/2004/07/Project";
(PD:ExportProjectDetailsMessage/PD:CPProjectId)[1]', 'varchar(max)' )
You may also want to consider not using varchar(max) but perhaps uniqueidentifier
A better way to do this is to simply declare the namespace before each of your queries:
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/Project')
It's like a temporary default. When you run the next query in the batch you'll get nulls again if you don't specify this before each of your selects.
So instead of using "SET", you can use "SELECT" to set the value like so:
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/Project')
SELECT #ProdID = #myDoc.value('(ExportProjectDetailsMessage/CPProjectId)[1]', 'VarChar(MAX)')
SELECT #ProdID
Same results, just more readable and maintainable.
I found the solution here: http://www.sqlservercentral.com/Forums/Topic967100-145-1.aspx#bm967325

SQL server insert data from table into an XML variable

How can I insert a whole bunch of rows into an XML variable without using a cursor?
I know I can do
SET #errors.modify('insert <error>{ sql:variable("#text") }</error> as last into /errors[1]')
to insert the value of a variable, but I want to basically do
SET #errors.modify(SELECT 'insert <error>{ sql:column("text") }</error>' FROM table)
which, of course, isn't legal syntax.
Edit: Obviously my question wasn't clear. What I want is to be able to do like this:
CREATE TABLE my_table(text nvarchar(50))
INSERT INTO my_table VALUES('Message 2')
INSERT INTO my_table VALUES('Message 3')
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
SET #errors.modify('INSERT EVERYTHING FROM my_table MAGIC STATEMENT')
And after running this code, #errors should contain
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>
Isn't this simpler?
set ErrorXML=(SELECT * from #MyTable FOR XML AUTO)
LAST UPDATE:
OK, now that the question is much clearer, he's the solution - hopefully!!
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
DECLARE #newErrors XML
SELECT #newErrors = (SELECT text AS 'error'
FROM dbo.my_table
FOR XML PATH(''), ELEMENTS)
SELECT #errors, #newErrors
SET #errors.modify('insert sql:variable("#newErrors") as last into (/errors)[1]')
SELECT #errors
This gives me
#errors at the beginning
<errors><error>Message 1</error></errors>
#newError after the "magic" SELECT:
<error>Message 2</error><error>Message 3</error>
#errors after the UPDATE:
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>
Is THAT what you're looking for?? :-)
(old answers - not what the OP was looking for.....)
You need to look at the .nodes() function in SQL XQuery - this will break up an XML
variable into a list of XML nodes, based on an XPath expression (that references some point in your XML where you are likely to have an enumeration of nodes of the same structure), and it gives them a "virtual" table and column name.
Based on that "Table.Column" element, you can select single values from that XML node - either attributes or sub-elements - and you get these back as "atomic" values, e.g. as INT, VARCHAR(x), whatever you need. These values can be inserted into the table:
INSERT dbo.YourTable(col1, col2, col3, ..., colN)
SELECT
Error.Column.value('#attr1[1]', 'varchar(20)'),
Error.Column.value('subitem[1]', 'int'),
.....
Error.Column.value('subitemN[1]', 'DateTime')
FROM
#xmldata.nodes('/error') AS Error(Column)
UPDATE: ok, so you want to do the opposite - turn relational data into XML - that's even easier :-)
DECLARE #NewXmlContent XML
SELECT #NewXmlContent =
(SELECT
col1 as '#ID',
col2 as 'SomeElement',
.....
colN as 'LastElement'
FROM
dbo.YourTable
WHERE
....
FOR XML PATH('element'), ROOT('root')
)
UPDATE YourOtherTable
SET XmlField.modify('insert sql:variable("#NewXmlContent")
as last into (/XPath)[1]')
WHERE (some condition)
This will give you something like this in #NewXmlContent:
<root>
<element ID="(value of col1)">
<SomeElement>(value of col2)</SomeElement>
.....
<LastElement>(value of colN)</LastElement>
</element>
</root>
and the UPDATE statement with the .modify() call will actually insert that content into an existing XML field in your database. This is the only way to get XML contents into an existing XML column - there's no way of directly referencing another XML column inside a XML fragment being inserted....
The new "FOR XML PATH" syntax is very powerful and flexible and allows you to do just about anything.
And of course, you can easily store that into a XML variable.
Marc
Based on marc's answer, here is a solution that works for SQL Server 2005:
CREATE TABLE #my_table(text nvarchar(50))
INSERT INTO #my_table VALUES('Message 2')
INSERT INTO #my_table VALUES('Message 3')
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
SELECT #errors = CAST(#errors AS nvarchar(max)) + '<new>' + (SELECT text AS 'error' FROM #my_table FOR XML PATH(''), ELEMENTS) + '</new>'
SET #errors = CAST(#errors AS nvarchar(max)) + '<new>' + #newErrors + '</new>'
SET #errors.modify('insert (/new/*) as last into (/errors)[1]')
SET #errors.modify('delete (/new)')
SELECT #errors
DROP TABLE #my_table
Will return
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>