Read XML Value in SQL Select Statement - sql-server

I'm trying to extract an XML value from a SQL Server column and put it into a SQL statement. My problem is that the documentation I've found doesn't explain how to do this if there are spaces or "" in the XML path.
I'm trying to extract the value property in the XML shown here (there is no namespace in the XML). The SQL Server column is called Settings:
<properties>
<settings hwid="stream:0.0.0">
<setting typeid="78622C19-58AE-40D4-8EEA-17351F4273B6">
<name>Codec</name>
<value>4</value>
</setting>
</settings>
</properties>

You can use OPENXML to retrieve data from xml, first create procedure like this:
CREATE PROCEDURE GetXmlValueProc
#xml NVARCHAR(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #hdoc INT;
EXEC sp_xml_preparedocument #hdoc OUTPUT, #xml;
DECLARE #Result NVARCHAR(50);
SELECT value
FROM
OPENXML(#hdoc, '/properties/settings/setting', 2)
WITH
(
value VARCHAR(100)
);
EXEC sp_xml_removedocument #hdoc;
END
GO
And call procedure in this way:
DECLARE #xml NVARCHAR(MAX)='<properties><settings hwid="stream:0.0.0"><setting typeid="78622C19-58AE-40D4-8EEA-17351F4273B6"><name>Codec</name><value>4</value></setting></settings></properties>'
EXEC dbo.GetXmlValueProc #xml
Even you can make procedure more generic and pass the xml path to get data.

I don't see any spaces in your XML. If you mean the various attributes, such as hwid, those are parsed separately from the node names. You can select those by prefacing with #.
I assume the type of the value node is int, if not you can change it below:
SELECT
t.Settings.value('(/properties/settings/setting/value)[1]', 'int'),
t.Settings.value('(/properties/settings/setting/#typeid)[1]', 'uniqueidentifier'),
t.Settings.value('(/properties/settings/#hwid)[1]', 'nvarchar(max)')
FROM myTable t
For reference, if you ever did have a node with a space in it: it would be encoded and a double-quote as "

Related

The label 'T14' has already been declared. Label names must be unique within a query batch or stored procedure

enter image description hereIncorrect syntax near '<'.
The label 'T14' has already been declared. Label names must be unique within a query batch or stored procedure.
The code follows:
DECLARE #XML AS XML
SELECT #XML = XMLData
FROM ReadXmlFile
WHERE IndexRow = #IndexRow;
Declare #Str Varchar(max)
SET #Str=(select cast(#XML as varchar(max)))
EXEC(#Str)
PRINT(#Str)
table ReadXmlFile is contains 3 column (IndexRow,XmlData,DateTime) that value of XmlData is filled by the user uploaded file
and #Str in Old StoredProcedure was:
Declare #Str Varchar(1000)
SET #Str='BULK INSERT #tbltest1
FROM ''//192.168.1.20/Softwares/' + #stfName + '/Reading/' + #FILE_NAME + '''
WITH
(
DATAFILETYPE =''char'',
Rowterminator=''\n''
--firstrow=10
)'
now i want #tbltest1 in part **SET #Str='BULK INSERT #tbltest1** fill by column XmlData from table ReadXmlFile Instead Fill by the following path '//192.168.1.20/Softwares/' + #stfName + '/Reading/' + #FILE_NAME +
this is part of the file XML:
<TestUniverseExport xmlns="http://www.omicron.at/dataexport">
<TM_Common>
<TestReportID>c522187c-2175-4b84-90b3-ebb6f854694c</TestReportID>
<TestReportOrder>1</TestReportOrder>
<Name>OMICRON Advanced Distance</Name>
<Version>2.40 </Version>
<Title>REL521-DE805.adt</Title>
<TestStartDate>1394-08-03T14:00:20+04:30</TestStartDate>
<TestEndDate>1394-08-03T14:02:45+04:30</TestEndDate>
<Offline>false</Offline>
<Overload>false</Overload>
<HWCReportOrder>0</HWCReportOrder>
<TOReportOrder>0</TOReportOrder>
<Assessment>PASSED</Assessment>
<ManualAssessment>false</ManualAssessment>
<PartiallyExecuted>false</PartiallyExecuted>
<Error>false</Error>
<TestStartMode>IMMEDIATELY</TestStartMode>
</TM_Common>
<TM_Dist>
<TestReportID>c522187c-2175-4b84-90b3-ebb6f854694c</TestReportID>
<TestReportOrder>1</TestReportOrder>
<TestModel>CONSTANT_CURRENT</TestModel>
What you show us, is not enough to answer your question properly. I must admit, I do not even see a question... And I doubt, that the message you get is really connected to this T14 within your dateTimes...
Your XML is - after adding some closing tags - perfectly okay:
DECLARE #xml XML=
N'<TestUniverseExport>
<TM_Common>
<TestReportID>c522187c-2175-4b84-90b3-ebb6f854694c</TestReportID>
<TestReportOrder>1</TestReportOrder>
<Name>OMICRON Advanced Distance</Name>
<Version>2.40 </Version>
<Title>REL521-DE805.adt</Title>
<TestStartDate>1394-08-03T14:00:20+04:30</TestStartDate>
<TestEndDate>1394-08-03T14:02:45+04:30</TestEndDate>
</TM_Common>
</TestUniverseExport>';
SELECT elmnts.value('local-name(.)','nvarchar(max)') AS ElementName
,elmnts.value('text()[1]','nvarchar(max)') AS ElementValue
FROM #xml.nodes('/TestUniverseExport/TM_Common/*') A(elmnts);
There must be something within the data, which brings up this error. Is XMLData a natively typed XML column?
And it is totally unclear what you are trying to get here:
Declare #Str Varchar(max)
SET #Str=(select cast(#XML as varchar(max)))
EXEC(#Str)
This is rather weird... Casting a XML to a string type will not result in an executable SQL-command...
Please try to add to your question and - if possible - provide a MCVE, to reproduce your issue.
UPDATE: After you edited your quesiton...
From the screenshot I take, that the table contains XML-typed values. And you try to change the old code in a way, that the XML is not taken from a file any more but directly out of that table. Correct so far?
If my assumptions are correct, this might be really trivial:
DECLARE #XML AS XML;
SELECT #XML = XMLData
FROM ReadXmlFile
WHERE IndexRow = #IndexRow;
Declare #Str Varchar(max);
SET #Str=cast(#XML as varchar(max));
The old code needed the dynamically created statement (together with EXEC() and PRINT) to load the XML from the file system. But now you have the XML directly in your table. So just take it, cast it and proceed from there...
What your own attempts did, was to execute something which was not a SQL-Command by any means...
You are executing an invalid sql query. See on how to use sql exec | execute
Wrap your query with the correct strings.
DECLARE #XML AS XML
SELECT #XML = XMLData
FROM ReadXmlFile
WHERE IndexRow = #IndexRow;
Declare #Str Varchar(max)
SET #Str='select '''+cast(#XML as varchar(max))+''''
EXEC(#Str)
PRINT(#Str)
or if you want to view it as xml.
SET #Str='select cast('''+cast(#XML as varchar(max))+''' as xml)'

MS SQL Server - OpenXML - Multiple elements

XML example:
<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>
I want to select both Risk elements with this query:
SELECT RISK
FROM OPENXML(#hDOC, 'POLICY/RISKS', 2)
WITH(
RISK XML 'RISK'
) AS Z
Expected:
1. <RISK><DRV>1</DRV></RISK>
2. <RISK><DRV>2</DRV></RISK>
Result:
1. <RISK><DRV>1</DRV></RISK>
(only first element was returned)
For comparison this query returns two rows as expected:
SELECT DRV
FROM OPENXML(#hDOC, 'POLICY/RISKS/RISK', 2)
WITH(
DRV XML 'DRV'
) AS Z
Result:
1. <DRV>1</DRV>
2. <DRV>2</DRV>
So the question is how can I get two Risk-rows?
Why are you not using the native XQuery support provided by SQL Server. OpenXML is old and having lot of issues.
You can write your query like following using XQuery Support
DECLARE #hDOC xml
SET #hDOC='<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>'
SELECT T.c.query('.') AS result
FROM #hDOC.nodes('/POLICY/RISKS/RISK') T(c)
GO
You will get output as
1. <RISK><DRV>1</DRV></RISK>
2. <RISK><DRV>2</DRV></RISK>
Edit: If you still want to do with OpenXml, use query like following.
DECLARE #DocHandle int
DECLARE #hDOC VARCHAR(1000)
SET #hDOC=N'<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>'
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #hDOC
SELECT RISK
FROM OPENXML(#DocHandle, 'POLICY/RISKS/RISK', 2)
WITH(
RISK XML '.'
) AS Z
EXEC sp_xml_removedocument #DocHandle
You will get the desired output.

The argument 1 of the XML data type method "query" must be a string literal

I have data in xml format in SQL Server. Now I am trying to find out single record based on my query. I am putting my code below,
declare #xml xml
declare #ID varchar
set #ID = '1'
set #xml = '
<row>
<Id>1</Id>
<name>OM</name>
</row>
<row>
<Id>2</Id>
<name>JAI</name>
</row>
<row>
<Id>2</Id>
<name>JAGDISH</name>
</row>
'
When I am executing my query, then it gives me a proper result (xml node):
Select #xml.query('/row[Id="1"]');
But When I am concatenating #ID to query, then it gives me an error:
Select #xml.query('/row[Id='+ #ID +']');
The error is:
The argument 1 of the XML data type method "query" must be a string literal.
You will need the sql:variable() XQuery extension function to refer to a variable. This function (quote from the link) "exposes a variable that contains a SQL relational value inside an XQuery expression".
Select #xml.query('/row[Id=sql:variable("#ID")]');
You could use dynamic SQL:
IF TRY_PARSE(#ID AS INT) IS NULL
THROW 50000, 'ID is not integer',1;
DECLARE #sql NVARCHAR(MAX) = 'Select #xml.query(''/row[Id=<placeholder>]'')';
SET #sql = REPLACE(#sql, '<placeholder>', #Id);
EXEC sp_executesql #sql, N'#xml XML', #xml;
Rextester Demo
Warning!
Be aware that you should always check user input.

Xquery delete with Xpath from sql:variable

I am having Xpath to delete from XML in an VARCHAR(MAX) variable, but XML.modify('delete '+#MyXpath) give an error ,
The argument 1 of the XML data type method "modify" must be a string
literal.
DECLARE #myXML XML,
#MyXpath VARCHAR(MAX)
-- Processing of Xpaths for components needed to remove
-- Adding those in #XpathsToRemove
SELECT TOP(1) #MyXpath = [XPATH]
FROM #XpathsToRemove
SET #myXML.modify('delete '+#MyXpath)
Is there any way to remove those components with Xpath available in #MyXpath variable ?
-- Edit Example XML
DECLARE #myXML XML ='<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Dont forget me this weekend!</body>
</note>'
,#MyXpath VARCHAR(MAX) = '//note/from'
-- There are many Xpaths and dynamically generated with some processing so I don't want to hardcode Xpath there
SET #myXML.modify('delete '+#MyXpath)
--This doesn't works too
-- SET #myXML.modify('delete "sql:variable("MyXPath")"')
SELECT #myXML
Thanks guys, I got solution by EXEC to generate dynamic queries.
Following is link that solved my issue,
How to using for loop using XQuery to delete xml nodes [duplicate]
Solution,
DECLARE #str nvarchar(MAX)
SET #str = 'SET #MyXML.modify('+char(39)+'delete '+#MyXPath+'/*'+char(39)+'); '
EXEC sp_executesql #str, N'#MyXML xml output', #MyXML output

Insert into xml in sql using modify method

I have to modify the existing xml in a table field value.
Each row value has different xml tags like
1 row.
'<root><comments><comment>comments1</comment><comment>comments2</comment></comments></root>'
2 row .
'<Users><User><Name>MAK</Name></User><User><Name>DANNY</User></Users>'
I need to add a tag <Resource>some ID</Resource>
after the root node.
like '<Users>**<Resource>some ID</Resource>**<User><Name>comments1</Name></User><User><Name>comments2</User></Users>'
I have tried with the below code .
declare #xml xml
set #xml = '<root><comments><comment>comments1</comment><comment>comments2</comment></comments></root>'
declare #Note varchar(10)
declare #insertnode nvarchar(100)
set #insertnode='commeressd'
declare #mainnode varchar(50)
set #mainnode='(//root)[1]'
set #Note = 'comment3'
SET #xml.modify('insert <Resource>{xs:string(sql:variable("#insertnode"))}</Resource> as first into {xs:string(sql:variable("#mainnode"))}')
select #xml
the expression after
into
is giving the error
XQuery [modify()]: Syntax error near '{'
..how do we specify this into expression also dynamically.
Any help would be appreciated.
You can use /*[1] to find the "first" root node where you want the insert to happen.
declare #xml xml
set #xml = '
<root>
<comments>
<comment>comments1</comment>
<comment>comments2</comment>
</comments>
</root>'
declare #insertnode nvarchar(100)
set #insertnode='ResourceID'
set #xml.modify('insert element Resource {sql:variable("#insertnode")} as first into /*[1]')
select #xml
Result:
<root>
<Resource>ResourceID</Resource>
<comments>
<comment>comments1</comment>
<comment>comments2</comment>
</comments>
</root>

Resources