SQL XML .Modify syntax - sql-server

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.

Related

T-SQL get value from XML

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]

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?

The execution of a SP in SSIS returns nothing

Until now I've been looking for a possible solution to the execution of a sp from SSIS, but anything seems to work. I´ve got a sp:
CREATE PROCEDURE [DBO].[SPIDENTIFIERS] #IDENT NVARCHAR(MAX) OUTPUT
What I need is to save the result in a variable that I've created in SSIS.
This is the configuration that I used to try to do it.
In the parameter set section I have also used the Direction as Output or ReturnValue but I received a error message. Just to try I put a Script Task to chek the value, but as you can see this is empty.
With the Direction Ouput or ReturnValue I've got this:
[Execute SQL Task] Error: Executing the query "EXECUTE spIdentifiers ? OUTPUT;" failed with the following error:
"El valor no está dentro del intervalo esperado.".
Possible failure reasons: Problems with the query, "ResultSet" property not set correctly,
parameters not set correctly, or connection not established correctly.
What am I missing in the configuration of the task?.
I looked for an answer in this post. But nothing seems to work
How do you call a Stored Procedure in SSIS?
SSIS Stored Procedure Call
Thanks in advance.
Your parameter should not be named, as #gerald Davis has indicated. For a connection manager of OLEDB type, it should be ordinal based, thus 0
Here's my sample package and you can see that my variable #[User::MyVariables] is populated with a lot of Xs
Here's my proc definition
IF NOT EXISTS
(
SELECT
*
FROM
sys.procedures AS P
WHERE
P.name = N'SPIDENTIFIERS'
)
BEGIN
EXECUTE sys.sp_executesql N'CREATE PROC dbo.spidentifiers AS SELECT ''stub version, to be replaced''';
END
GO
ALTER PROCEDURE [DBO].[SPIDENTIFIERS]
(
#IDENT NVARCHAR(MAX) OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
SET #IDENT = REPLICATE(CAST(N'X' AS nvarchar(MAX)), 4001);
-- Uncomment this to watch the fireworks
--SET #IDENT = REPLICATE(CAST(N'X' AS nvarchar(MAX)), 4001);
END
Biml
I'm a big fan of using Biml, the Business Intelligence Markup Language, to describe my solutions as it allows the reader to recreate exactly the solution I describe without all those pesky mouse clicks.
Download BIDS Helper and install or unzip
Add a new biml file to your SSIS project
Fix the third line's ConnectionString to point to a valid server and database. Mine references localhost\dev2014 and tempdb
Right click on the saved biml file and generate package
Take your well deserved Biml break
Biml code follows
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Connections>
<OleDbConnection Name="tempdb" ConnectionString="Provider=SQLNCLI11.1;Server=localhost\dev2014;Initial Catalog=tempdb;Integrated Security=SSPI;" />
</Connections>
<Packages>
<Package Name="so_30460630" ConstraintMode="Linear">
<Variables>
<Variable DataType="String" Name="MyVariables">0</Variable>
</Variables>
<Tasks>
<ExecuteSQL
ConnectionName="tempdb"
Name="SQL Ensure Objects Exist">
<DirectInput>
<![CDATA[IF NOT EXISTS
(
SELECT
*
FROM
sys.procedures AS P
WHERE
P.name = N'SPIDENTIFIERS'
)
BEGIN
EXECUTE sys.sp_executesql N'CREATE PROC dbo.spidentifiers AS SELECT ''stub version, to be replaced''';
END
GO
ALTER PROCEDURE [DBO].[SPIDENTIFIERS]
(
#IDENT NVARCHAR(MAX) OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
SET #IDENT = REPLICATE(CAST(N'X' AS nvarchar(MAX)), 4001);
END
]]>
</DirectInput>
</ExecuteSQL>
<ExecuteSQL
ConnectionName="tempdb"
Name="SQL Using an OUTPUT parameter">
<DirectInput>EXECUTE dbo.SPIDENTIFIERS ? OUTPUT;</DirectInput>
<Parameters>
<Parameter DataType="String" VariableName="User.MyVariables" Name="0" Direction="Output" Length="-1" />
</Parameters>
</ExecuteSQL>
<ExecuteSQL
ConnectionName="tempdb"
Name="SQL Breakpoint">
<DirectInput>SELECT NULL AS nothing;</DirectInput>
</ExecuteSQL>
</Tasks>
</Package>
</Packages>
</Biml>
Your stored procedure parameter is OUTPUT but your SSIS package defines it as INPUT. Depending on the application, RETURNVALUE could also be used but from the syntax of your SP it is using an Output Parameter not a Return Value.
Verify the User:Id variable has the correct datatype. Try executing the SP in SSMS manually to verify that it runs without error and returns the expected result.
Also I think you are mixing OLEDB and ADO.NET syntax.
If using an OLEDB Data connection then you use the ? parameters in the query and the Parameter names must be "Parameter0 (and Parameter1, etc if more than 1)". Note: parameter names are zero indexed. In SP with more than 1 parameter the correct order is required.
If using an ADO.NET DataConnection then the query is just the named of the stored procedure, IsStoredProcedure=True, and the Parameter names matches the name of the parameter in the SP.
From your screenshots you currently are using named parameters and OLDEDB ? syntax. I don't believe that is ever valid. It is one or the other depending on the connection type.
UserID needs to be in the readwritevariable section, not the read section, so that you allow the task to write into the variable.
parameter direction should be "output" since you are passing it out of your task not into it.
You need to keep the sql statement as "EXEC SPIDENTIFIERS ? OUTPUT**
direction of variable should be Output in parameter mapping tab and "Parameter Name" should be exactly same as of input parameter defined in stored procedure or you can just use 0 instead of giving the actual name.

Using Xquery to replace a node value that is xsi:nil = "true"

I am trying to use XQuery in SQL Server 2005 to update xml saved in a column. Here is a sample of the data I need to update.
<Length>3</Length>
<Width>5</Width>
<Depth>6</Depth>
<Area xsi:nil="true" />
<Volume xsi:nil="true" />
I need to set the area and volume to values from a different table. I am creating a CTE for the update. There is other logic that I have omitted, but I have verified that the CTE contains the correct data for the update:
;with Volume (DocumentID, Volume) As
(
Select DocumentID, Volume from tbl
)
and I am using the following XQuery SQL statement to try to update the table.
UPDATE tbl_Archive
SET XML.modify(' declare namespace x="http://www.redacted.com";
replace value of (/x:Document/x:Volume/text())[1]
with sql:column("Volume.Volume")')
From Volume where volume.documentID = tbl_Archive.DocumentID
I get 1 row affected, but when I look at the XML it hasn't changed, and I can't figure out what needs to be fixed to make it work. The node is untyped, if that makes any difference.
Update wont work if there's no text to replace.. the XPath /x:Document/x:Volume/text())[1] will return an empty set.
Try insert...
UPDATE tbl_Archive
SET XML.modify(' declare namespace x="http://www.redacted.com";
insert text {sql:column("Volume.Volume")}
as first into (/x:Document/x:Volume)[1]')
From Volume where volume.documentID = tbl_Archive.DocumentID
..you'll then need to remove the nil="true" attribute..
Something like this maybe..
update tbl_Archive set XML.modify('delete /*:Document/*:Volume[text()]/#xsi:nil')

Resources