Xquery delete with Xpath from sql:variable - sql-server

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

Related

Read XML Value in SQL Select Statement

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 "

Remove Escaped character from XML in SQL Server

How can the & be shown inside the XML field?
DECLARE #xml XML
SET #xml = (SELECT 'test&test' AS xml_entity
FOR XML PATH('xml_merge'),TYPE)
SELECT #xml
The result is showing a &
<xml_merge> <xml_entity>test&test</xml_entity> </xml_merge>
I also tried with CDATA tag but that didn't solve it:
DECLARE #xml XML
SET #xml = (SELECT '<![CDATA[test&test]]>' AS xml_entity
FOR XML PATH('xml_merge'),TYPE)
SELECT #xml
<xml_merge>
<xml_entity><![CDATA[test&test]]></xml_entity>
</xml_merge>
How to handle this? I need to show the & instead of &
Expected result
<xml_merge> <xml_entity>test&test</xml_entity> </xml_merge>
Ampersand is a special character in XML. That's why it requires its entity &
Please try the following SQL. It will show you that after the SELECT ... as a VARCHAR(), the entity is gone. It remains as such just inside the XML data type.
You can check it here: 2.4 Character Data and Markup
SQL
DECLARE #xml XML;
SET #xml = (SELECT 'test & test' AS xml_entity
FOR XML PATH('xml_merge'),TYPE);
SELECT #xml;
SELECT #xml.value('(/xml_merge/xml_entity/text())[1]','VARCHAR(100)') AS xml_entity;
Output
xml_entity
test & test
This seems like an X/Y problem. You want invalid XML to be returned as XML. If you want the string value then you can pull it from the XML as a value and the escape characters will be removed, however, if you want invalid XML the way you outlined it above then you will have to convert the XML to a string and then replace the escapes.
DECLARE #xml XML
SET #xml = (SELECT 'test&test' AS xml_entity
FOR XML PATH('xml_merge'),TYPE)
SELECT #xml
DECLARE #XmlBlob NVARCHAR(MAX)=CAST(#Xml AS NVARCHAR(MAX))
SELECT REPLACE(#XmlBlob,'&','&')
<xml_merge><xml_entity>test&test</xml_entity></xml_merge>

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

SQL XML value filtering using variable [duplicate]

I have a SQL function that takes a variable called attribute, which is the xml attribute I want to get the value from. xmlPath is the full XML string.
My xml looks like this:
<EventSpecificData>
<Keyword>
<Word>myWord</Word>
<Occurences>1</Occurences>
<Context>context</Context>
</Keyword>
</EventSpecificData>
I want to get the value for <Word>, so I pass in /Keyword/Word and set a variable to:
set #value = #xmlPath.value('(/EventSpecificData/#attribute)[1]', 'varchar(max)')
However, I don't think #attribute is actually inserting the variables string. Is there another way to do this?
Here are a couple of solutions for you.
Sample data:
declare #xml xml
set #xml =
'<EventSpecificData>
<Keyword>
<Word>myWord</Word>
<Occurences>1</Occurences>
<Context>context</Context>
</Keyword>
</EventSpecificData>'
Get the first value from node named Word regardless of parents. Use // to do a deep search and use local-name() to match node name.
declare #Attribute varchar(max)
set #Attribute = 'Word'
select #xml.value('(//*[local-name() = sql:variable("#Attribute")])[1]', 'varchar(max)')
Provide parent node name and attribute in separate variables using local-name() in two levels.
declare #Node varchar(max)
declare #Attribute varchar(max)
set #Attribute = 'Word'
set #Node = 'Keyword'
select #xml.value('(/EventSpecificData
/*[local-name() = sql:variable("#Node")]
/*[local-name() = sql:variable("#Attribute")])[1]', 'varchar(max)')
Since the parameter to nodes have to be a string literal it invites to use dynamic sql to solve this. It could look something like this to make it work with your original variable content.
set #Attribute = 'Keyword/Word'
declare #SQL nvarchar(max)
set #SQL = 'select #xml.value(''(/EventSpecificData/'+#Attribute+')[1]'', ''varchar(max)'')'
exec sp_executesql #SQL, N'#xml xml', #xml
But you should be aware of that if you use this you are wide open to SQL Injection attacks. Some devious end-user might come up with a attribute string that looks like this:
set #Attribute = 'Keyword/Word)[1]'', ''varchar(max)'') select ##version --'
Executing the dynamic SQL with that will give you two result sets. The select ##version is just there to show some harmless code but it might be much worse stuff in there.
You can use quotename() to prevent the SQL injection attack. It will at least prevent the attempt made by me.
set #Attribute = 'Keyword/Word'
set #SQL = 'select #xml.value('+quotename('(/EventSpecificData/'+#Attribute+')[1]', '''')+', ''varchar(max)'')'
exec sp_executesql #SQL, N'#xml xml', #xml
Is the last version using quotename()safe? Have a look at this article by Erland Sommarskog The Curse and Blessings of Dynamic SQL.
Quote:
So with quotename() and quotestring(), do we have as good protection
against SQL injection as we have with parameterised commands? Maybe. I
don't know of any way to inject SQL that slips through quotename() or
quotestring(). Nevertheless, you are interpolating user input into the
SQL string, whereas with parameterised commands, you don't.
Try concatenating the string.
set #value = #xmlPath.value('(/EventSpecificData/' + #attribute + ')[1]', 'varchar(max)')
Updated answer:
Let's try CASE'ing the operation.
SELECT #value = CASE #attribute
WHEN 'word' THEN [word]
WHEN 'occurrence' THEN [occurrence]
WHEN 'context' THEN [context]
END AS [value]
FROM
(
SELECT x.u.value('(/EventSpecificData/Keyword/Word)[1]', 'varchar(max)') AS [word]
, x.u.value('(/EventSpecificData/Keyword/Occurrence)[1]', 'varchar(max)') AS [word]
, x.u.value('(/EventSpecificData/Keyword/Context)[1]', 'varchar(max)') AS [word]
FROM #xmlPath.nodes('/EventSpecificData') x(u)
) a

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