Query to XML Node is giving me NULL Value - sql

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.

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

Parsing out xml column(varchar) within a table which needs to create a view

#xml = N'<School>
<Name>
<Id>123456</ID>
</Name>
<Grade>
<FirstGrade>80</FirstGrade>
</Grade>
</School>',
This part above is a varchar column in Schooltable which contain several other columns. I just want to parse schooldataxml which contains xml and formatted as varchar.
This is what I have tried
create view school as
select CAST(CAST(schooldataxml as nvarchar(max)) as XML).query('school/Name/Id') Col
from schooltable
Here I am trying to cast it as cast statement however it doesn't take out the html tags. I have tried nodes too but got no success
I am getting this
<Id>123456</ID>
I want to get this for each row in a new column in my new view
123456
Please note that I need to parse every row into my new column one by one. So the next value that needs to be parse out is different- dynamic values.
Welcome to StackOverflow.
Note this code:
DECLARE #xml XML =
N'<School>
<Name>
<ID>123456</ID>
</Name>
<Grade>
<FirstGrade>80</FirstGrade>
</Grade>
</School>';
SELECT #xml.query('/School/Name/ID'); --<< THIS
SELECT #xml.query('(/School/Name/ID/text())[1]'); --<< NEEDS TO BE THIS
SELECT #xml.value('(/School/Name/ID/text())[1]','VARCHAR(100)'); -- EVEN BETTER
With query you need to specify the node type in your XPath expression; the default behavior is to return an XML node.
For what you are doing, the value method will likely yield better performance.
Against a table:
DECLARE #schooltable TABLE (schooldataxml XML);
INSERT #schooltable VALUES(
N'<School>
<Name>
<ID>123456</ID>
</Name>
<Grade>
<FirstGrade>80</FirstGrade>
</Grade>
</School>'),(
N'<School>
<Name>
<ID>555444</ID>
</Name>
<Grade>
<FirstGrade>90</FirstGrade>
</Grade>
</School>');
SELECT SomeId = s.schooldataxml.value('(/School/Name/ID/text())[1]','VARCHAR(100)')
FROM #schooltable AS s;
Returns:
SomeId
----------
123456
555444

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()

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.

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>