T-SQL get value from XML - sql-server

I am trying to query XML (SSRS) in SQL Server; I want to get the value from the TO in this case.
<ParameterValues>
<ParameterValue>
<Name>TO</Name>
<Value>PacoAT#Stack.com</Value>
</ParameterValue>
<ParameterValue>
<Name>RenderFormat</Name>
<Value>EXCEL</Value>
</ParameterValue>
</ParameterValues>
I have tried a few queries but cant seem to get to that level.

You can do it using XQuery
SELECT
[TO] = t.XmlColumn.value('(ParameterValues/ParameterValue[Name/text() = "TO"]/Value/text())[1]', 'varchar(100)')
FROM YourTable t
/ is a child node navigation. [] is a predicate test on a particular node. So this looks for ParameterValues/ParameterValue which has a Name child with text TO and returns the Value child's text.
Note the use of text() rather than relying on implicit conversion/atomization. Also .value needs to be guaranteed a single result, so needs [1]

Related

Update Node Value XML T-SQL Based on Value of Other Node at Same Level

I would like to update a piece of xml, which I hold in an variable. I would like to update the value of Value where Name is a given value. Here's a mockup of the xml:
<ParameterValues>
<ParameterValue>
<Name>TO</Name>
<Value>me#you.com</Value>
</ParameterValue>
<ParameterValue>
<Name>CC</Name>
<Value>bob#email.com</Value>
</ParameterValue>
<ParameterValue>
<Name>Priority</Name>
<Value>NORMAL</Value>
</ParameterValue>
</ParameterValues>
I have written the below, which always updates the first value, but I'd like to change the modify command so that I have the equivalent to a where clause - I would like to update (for instance) the value where Name = "CC" in the same node. How do I achieve this?
All the examples I see online assume I have a unique identifier for each node in the tree structure above the value I want to update, not at the same level.
DECLARE #Email VARCHAR(50) = 'New#email.co.uk';
DECLARE #Ext XML =
'<ParameterValues>
<ParameterValue>
<Name>TO</Name>
<Value>me#you.com</Value>
</ParameterValue>
<ParameterValue>
<Name>CC</Name>
<Value>bob#email.com</Value>
</ParameterValue>
<ParameterValue>
<Name>Priority</Name>
<Value>NORMAL</Value>
</ParameterValue>
</ParameterValues>';
SET #Ext.modify('replace value of (//Value/text())[1] with sql:variable("#Email")');
SELECT
Col.value('./Name[1]','VARCHAR(100)') AS [Name]
,Col.value('./Value[1]','VARCHAR(1000)')AS [Value]
FROM
#Ext.nodes('//ParameterValue') AS Tab(Col);
All help gratefully received!
Try it like this
DECLARE #Ext XML =
'<ParameterValues>
<ParameterValue>
<Name>TO</Name>
<Value>me#you.com</Value>
</ParameterValue>
<ParameterValue>
<Name>CC</Name>
<Value>bob#email.com</Value>
</ParameterValue>
<ParameterValue>
<Name>Priority</Name>
<Value>NORMAL</Value>
</ParameterValue>
</ParameterValues>';
DECLARE #Email VARCHAR(50) = 'New#email.co.uk';
DECLARE #SearchFor VARCHAR(100)='CC'
SET #Ext.modify('replace value of (/ParameterValues/ParameterValue[Name=sql:variable("#Searchfor")]/Value/text())[1] with sql:variable("#Email")');
SELECT #Ext;
The idea in short.
The .modify() method can change only one place per call. That means, that the first argument (the XPath after replace value of) must be a singleton. We need a second variable to define which parameter we want to change. Here I use:
replace value of (/ParameterValues
/ParameterValue[Name=sql:variable("#Searchfor")]
/Value/text())[1]
with sql:variable("#Email")
You can read this as Dive into <ParameterValues>, then into <ParameterValue> and find one with the given Name. Below we dive into <Value> and return the text().
If there are multiple occurances we'll pick the first in any case and replace it with the given value.
See following snippet:
SET #Ext.modify('replace value of (//Value[../Name="CC"]/text())[1] with sql:variable("#Email")');
The trick is to apply condition in square brackets.

How to update password in all Windows File Share deliveries in SSRS

I have around 20 Windows File Share subscriptions. Every time the password is changed - I have to manually update password for each of that file share subscription.
Is any easier way of doing that?
Like update it in one place and it will automatically update in all file share subscriptions?
There doesn't seem to be an easier way - yet. I can give you some of the pieces you need to make a way though.
The username and password data is stored as XML in the ExtensionSettings field ReportServer.dbo.Subscriptions table.
Unfortunately, the username and password are stored as
<ParameterValues>
<ParameterValue>
<Name>PATH</Name>
<Value>\\SERVER_NAME\files\TEST\Automation\Dropoff</Value>
</ParameterValue>
<ParameterValue>
<Name>FILENAME</Name>
<Value>MEMBERS</Value>
</ParameterValue>
<ParameterValue>
<Name>FILEEXTN</Name>
<Value>True</Value>
</ParameterValue>
<ParameterValue>
<Name>USERNAME</Name>
<Value>AABBEItDo8/msevjegsUsAlJf5eo1fTFjFe16FNk94Z+7hQvqsSHgW93DHqSL3rF2iHOCiwkvV9kEFGeUMG0tW4VV6gdQghXup+2V3BZHwo=</Value>
</ParameterValue>
<ParameterValue>
<Name>PASSWORD</Name>
<Value>AABBpjdpbU2qN46RzMvYGfVPIW5B3cOlFtzXeIo8Sfn3uOSH5LiXbfptxYuFMNgR+5uYpHnh+6Y5oY4GDn6TXwOSYa5La+40</Value>
</ParameterValue>
<ParameterValue>
<Name>RENDER_FORMAT</Name>
<Value>PDF</Value>
</ParameterValue>
<ParameterValue>
<Name>WRITEMODE</Name>
<Value>Overwrite</Value>
</ParameterValue>
</ParameterValues>
I haven't found a way to figure out what the hashed values should be without changing one first and then looking at the Extension Settings to find the Password. You can update one manually and then view the new value to update the rest.
You will need a loop to go through the subscriptions that you want one at a time.
Here's some code to update the Password:
DECLARE #PASSWORD AS VARCHAR(150) = 'AABBpjdpbU2qN46RzMvYGfVPIW5B3cOlFtzXeIo8Sfn3uOSH5LiXbfptxYuFMNgR+5uYpHnh+6Y5oY4GDn6TXwOSYa5La+40'
DECLARE #EXTENSION_SETTINGS XML
--Add loop to go through all subscriptions
SELECT #EXTENSION_SETTINGS = ExtensionSettings
FROM ReportServer.dbo.Subscriptions
WHERE SubscriptionID = #SUBSCRIPTION_ID;
SET #EXTENSION_SETTINGS.modify('replace value of (/ParameterValues/ParameterValue[Name="PASSWORD"]/Value/text())[1] with sql:variable("#PASSWORD")');
UPDATE dbo.Subscriptions
SET ExtensionSettings = CAST(#EXTENSION_SETTINGS AS VARCHAR(8000))
WHERE SubscriptionID = #SUBSCRIPTION_ID
--Next in Loop
I see that every time a new subscription is created the hashed value of user name and password are different for the previous subscription even though I use the same user name and password. We have around 600 subscriptions created to write reports to a common file share using the same account to access it, however, each of these 600 subscriptions has a different hashed value for user name and password.
What is actually determining these hashed values?

SQL XML .Modify syntax

I'm trying to figure out what wrong with my XML update query. I don't have much experience working with XML in SQL but I thought this should work.
I want to update a value of the PATH value in XML.
Here's what I am currently trying:
DECLARE #PATH VARCHAR(500) = 'TEST'
IF OBJECT_ID('tempdb..#SUBSCRIPTION', 'U') IS NOT NULL
DROP TABLE #SUBSCRIPTION;
CREATE TABLE #SUBSCRIPTION (ExtensionSettings xml)
INSERT #SUBSCRIPTION
( ExtensionSettings )
SELECT '<ParameterValues><ParameterValue><Name>PATH</Name><Value>\\valinor\group\XFER</Value></ParameterValue><ParameterValue><Name>FILENAME</Name><Value>REPORT CARD TEST - SITE - YYYYMMDD</Value></ParameterValue><ParameterValue><Name>FILEEXTN</Name><Value>True</Value></ParameterValue><ParameterValue><Name>RENDER_FORMAT</Name><Value>PDF</Value></ParameterValue><ParameterValue><Name>WRITEMODE</Name><Value>Overwrite</Value></ParameterValue></ParameterValues>'
SELECT LEN(CAST(ExtensionSettings AS varchar(8000))) DL_XML, CAST(ExtensionSettings AS varchar(8000)) EXT_XML
FROM #SUBSCRIPTION
-- UPDATE ATTRIBUTE
UPDATE #SUBSCRIPTION
SET ExtensionSettings.modify('replace value of (/ParameterValues/ParameterValue/#Name[.="PATH"])[1] with sql:variable("#PATH")')
SELECT LEN(CAST(ExtensionSettings AS varchar(8000))) DL_XML, CAST(ExtensionSettings AS varchar(8000)) EXT_XML
FROM #SUBSCRIPTION
SQL Server says 1 row affected but nothing changes.
I need to update other columns too but assume once I get this working the others will work the same way.
Can you help me and point out what I am doing incorrectly? I am trying to automate some SSRS file share subscriptions but the only examples I find are for e-mail and they edit the XML data as text.
Below is the XML I'm working with in a readable format:
<ParameterValues>
<ParameterValue>
<Name>PATH</Name>
<Value>\\valinor\group\XFER</Value>
</ParameterValue>
<ParameterValue>
<Name>FILENAME</Name>
<Value>REPORT CARD TEST - SITE - YYYYMMDD</Value>
</ParameterValue>
<ParameterValue>
<Name>FILEEXTN</Name>
<Value>True</Value>
</ParameterValue>
<ParameterValue>
<Name>RENDER_FORMAT</Name>
<Value>PDF</Value>
</ParameterValue>
<ParameterValue>
<Name>WRITEMODE</Name>
<Value>Overwrite</Value>
</ParameterValue>
</ParameterValues>
After a couple of hours of trial and error, I have figured out the syntax to update the value of one element based on the value of another element in the same node^. I wasn't able to find a similar example.
-- UPDATE PATH ATTRIBUTE
UPDATE #SUBSCRIPTION
SET ExtensionSettings.modify('replace value of (/ParameterValues/ParameterValue[Name="PATH"]/Value/text())[1] with sql:variable("#PATH")')
-- UPDATE FILENAME ATTRIBUTE
UPDATE #SUBSCRIPTION
SET ExtensionSettings.modify('replace value of (/ParameterValues/ParameterValue[Name="FILENAME"]/Value/text())[1] with sql:variable("#FILENAME")')
Before:
<ParameterValues>
<ParameterValue><Name>PATH</Name><Value>\\valinor\group\XFER</Value></ParameterValue>
<ParameterValue><Name>FILENAME</Name><Value>REPORT CARD TEST - YYYMMDD</Value></ParameterValue>
<ParameterValue><Name>FILEEXTN</Name><Value>True</Value></ParameterValue>
<ParameterValue><Name>RENDER_FORMAT</Name><Value>PDF</Value></ParameterValue>
<ParameterValue><Name>WRITEMODE</Name><Value>Overwrite</Value></ParameterValue>
</ParameterValues>
After:
<ParameterValues>
<ParameterValue><Name>PATH</Name><Value>\\valinor\group\XFER\TEST\Site1</Value></ParameterValue>
<ParameterValue><Name>FILENAME</Name><Value>0123456X - 20180615</Value></ParameterValue>
<ParameterValue><Name>FILEEXTN</Name><Value>True</Value></ParameterValue
<ParameterValue><Name>RENDER_FORMAT</Name><Value>PDF</Value></ParameterValue>
<ParameterValue><Name>WRITEMODE</Name><Value>Overwrite</Value></ParameterValue>
</ParameterValues>
Now if I could just figure out how to combine these into one statement. But close enough for now.
^Please feel free to correct any incorrect wording.

SQL Server : read XML data

I have this query taken from the site www.SQLauthority.com:
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red</Color5>
</Colors>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
</SampleXML>'
SELECT
a.b.value('Colors[1]/Color1[1]','varchar(10)') AS Color1,
a.b.value('Colors[1]/Color2[1]','varchar(10)') AS Color2,
a.b.value('Colors[1]/Color3[1]','varchar(10)') AS Color3,
a.b.value('Colors[1]/Color4[1]/#Special','varchar(10)')+' '+
+a.b.value('Colors[1]/Color4[1]','varchar(10)') AS Color4,
a.b.value('Colors[1]/Color5[1]','varchar(10)') AS Color5,
a.b.value('Fruits[1]/Fruits1[1]','varchar(10)') AS Fruits1,
a.b.value('Fruits[1]/Fruits2[1]','varchar(10)') AS Fruits2,
a.b.value('Fruits[1]/Fruits3[1]','varchar(10)') AS Fruits3,
a.b.value('Fruits[1]/Fruits4[1]','varchar(10)') AS Fruits4
FROM #MyXML.nodes('SampleXML') a(b)
I am not getting a better picture of how the nodes fetching from the xml data.
I have few queries regarding this.
what is a(b) in this?
how the structure will change if i have another node inside colors and all the existing child nodes appended to that?
ie:
<Colorss>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red</Color5>
</Colors>
<Colorss>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
what does it mean by a.b.value? When I mouse over it shows a is derived table. Can I check value of the table a?
Any help in this will be appreciated.
what is a(b) in this?
The call to .nodes('SampleXML') is a XQuery function which returns a pseudo table which contains one column of an XML fragment for each of the elements that this XPath expression matches - and the a(b) is the table alias (a) for that column, and b is the name of the column in that pseudo table containing the XML fragments.
what does it mean by a.b.value?
This is based on the above - a is the table alias for that temporary, inline pseudo table, b is the column name for the column in that table, and .value() is another XQuery function that will extract a single value from XML, based on the XPath expression (first argument) and it will return it as the datatype specified in the second argument.
You should check out those introductions to XQuery support in SQL Server to understand better:
Introduction to XQuery in SQL Server 2005
XQuery basics
and there are numerous other introductions and tutorials on XQuery - just search with your favorite search engine and you'll get tons of hits!
here's my stab # it:
a-refers to root;b-refers to root and child node
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red
<Color6>Black44</Color6>
<Color7>Black445</Color7>
</Color5>
</Colors>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
</SampleXML>'
to get an inner child
SELECT
a.c.value('Colors1/Color11','varchar(10)') AS Color1,
a.c.value('Colors1/Color21','varchar(10)') AS Color2,
a.c.value('Colors1/Color31','varchar(10)') AS Color3,
a.c.value('Colors1/Color41/#Special','varchar(10)') AS Color4,
a.c.value('Colors1/Color51','varchar(10)') AS Color5,
a.c.value('Colors1/Color51/Color71','varchar(50)') AS Color6a,
a.c.value('Colors1/Color51/Color61','varchar(50)') AS Color6b, a.c.value('Fruits1/Fruits11','varchar(10)') AS Fruits1,
a.c.value('Fruits1/Fruits21','varchar(10)') AS Fruits2,
a.c.value('Fruits1/Fruits31','varchar(10)') AS Fruits3,
a.c.value('Fruits1/Fruits41','varchar(10)') AS Fruits4
FROM #MyXML.nodes('SampleXML') a(c)
A nodes() method invocation with the query expression /root/Color(n) would return a rowset with three rows, each containing a logical copy of the original XML document, and with the context item set to one of the nodes
see here

How can I get Sql Server 2K8 R2 to return this xml without resorting to compatability mode

We are in the process of upgrading our sql server to 2K8 R2 and the output of a FOR XML AUTO query has changed.
The query outputs columns from three tables
The resultset returns three rows which each column is identical bar the last two columns from the third table. the results used to show as below
<element1 myval="Test">
<element2 myotherval="atest">
<element3 a="a"/>
<element3 a="b"/>
<element3 a="c" />
</element2>
</element1>
it not shows
<element1 myval="Test">
<element2 myotherval="atest">
<element3 a="a"/>
</element2>
</element1>
<element1 myval="Test">
<element2 myotherval="atest">
<element3 a="B"/>
</element2>
</element1>
<element1 myval="Test">
<element2 myotherval="atest">
<element3 a="C"/>
</element2>
</element1>
I have been trying to use For XML Path but it still returns 3 separate instances of element1 rather than grouping the data.
If you want a subtree using FOR XML PATH, you'll have to write subqueries for each subtree. So in your case you have a parent select statement for element1, and one of the columns is a subquery that gets whatever needs to be in element2 (which in turn can also be subqueries). If you use subqueries and you want XML returned from those, use
FOR XML PATH('elementN'), TYPE
or it'll escape the XML code.

Resources