Wednesday 11 April 2012

Data-Driven Subcriptions in Standard Edition of SQL Server

Recently I was asked to come up with a solution where we could emulate the usefulness of a data-driven subscription without paying out for the Enterprise edition of SQL Server.  This was the result.

The idea is to create an ‘empty’ standard subscription in SSRS and then, using a stored procedure, manipulate the parameters and run the report.
First create a dummy subscription that isn’t scheduled to run:

Set the parameters and delivery information as above.  For now we will use only one parameter, but the stored procedure can easily be changed to accommodate as many as required.

Once the subscription is saved, you need to get the SubscriptionID from the report server. Use the following code:

SELET
c.Name AS ReportName,   
rs.ScheduleID
FROM ReportServer.dbo.[Catalog] c
INNER JOIN ReportServer.dbo.Subscriptions s ON c.ItemID = s.Report_OID
INNER JOIN ReportServer.dbo.ReportSchedule rs ON c.ItemID = rs.ReportID
AND rs.SubscriptionID = s.SubscriptionID
Where
c.Name = 'Subscription Name'

You’ll need the SubscriptionID when calling the report.
Next create the following stored procedure that will be called to run the report(s):
(I found this SP on the internet, but can’t remember where so can’t credit where it’s due), either way it has a couple of changes and I hope it helps out other people)
USE [ReportServer]
GO
/****** Object:  StoredProcedure [dbo].[data_driven_subscription]    Script Date: 04/11/2012 16:38:33 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER   procedure [dbo].[data_driven_subscription_1_Param]
       ( @scheduleID uniqueidentifier,
         @TO varchar (2000) = ' ',
         @CC varchar (2000) = ' ',
         @BCC varchar (2000) = ' ',
         @ReplyTO varchar (2000) = ' ',
         @BODY varchar (8000) = ' ',
         @param1 varchar (256) = ' '
       )
as

DECLARE
       @ptrval binary(16),
       @PARAMptrval binary(16),
       @TOpos int,
       @CCpos int,
       @BCCpos int,
       @RTpos int,
       @BODYpos int,
       @PARAM1Pos int,
       @length int,
       @subscriptionID uniqueidentifier


       -- set the subscription ID
       SELECT @subscriptionID = SubscriptionID
       FROM ReportSchedule WHERE ScheduleID = @scheduleID
      
       -- set the text point for this records Email info
       SELECT @ptrval = TEXTPTR(ExtensionSettings)
       FROM Subscriptions WHERE SubscriptionID = @subscriptionID


       -- set the text point for this records Parameter info
       SELECT @PARAMptrval = TEXTPTR(Parameters)
       FROM Subscriptions WHERE SubscriptionID = @subscriptionID
      
              -- set the start position for the TO Address
              SELECT @TOpos = patindex('%|EmailTO|%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             

                     IF isnull(@TOpos, '') <> '' and @TOpos > 0 and len(@To) > 0
                           -- change the TO address
                           UPDATETEXT Subscriptions.ExtensionSettings
                                   @ptrval
                                  @TOpos
                                  4
                                  @To

              -- set the start position for the CC Address
              SELECT @CCpos = patindex('%|CarbonCopy|%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             

                     IF isnull(@CCpos, '') <> '' and @CCpos > 0 and len(@CC) > 0
                           -- change the TO address
                           UPDATETEXT Subscriptions.ExtensionSettings
                                  @ptrval
                                  @CCpos
                                  4
                                  @CC
      
              -- set the start position for the BCC Address
              SELECT @BCCpos = patindex('%|BlindCopy|%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             

                     IF isnull(@BCCpos, '') <> '' and @BCCpos > 0 and len(@BCC) > 0
                           -- change the TO address
                           UPDATETEXT Subscriptions.ExtensionSettings
                                  @ptrval
                                  @BCCpos
                                  4
                                  @BCC

              -- set the start position for the REPLY TO Address
              SELECT @RTpos = patindex('%|ReplyTo|%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             

                     IF isnull(@RTpos, '') <> '' and @RTpos > 0 and len(@ReplyTO) > 0
                           -- change the REPLY TO address
                           UPDATETEXT Subscriptions.ExtensionSettings
                                  @ptrval
                                  @RTpos
                                  4
                                  @ReplyTO

              -- set the start position for the BODY Text
              SELECT @BODYpos = patindex('%|Comment|%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
      
                     IF isnull(@BODYpos, '') <> '' and @BODYpos > 0 and len(@BODY) > 0
                           -- change the REPLY TO address
                           UPDATETEXT Subscriptions.ExtensionSettings
                                  @ptrval
                                  @BODYpos
                                  4
                                  @BODY

              -- set the start position for the Parameter 1
              SELECT @PARAM1Pos = patindex('%|Parameter1|%', Parameters) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
     
              IF isnull(@PARAM1Pos, '') <> '' and @PARAM1Pos > 0 and len(@parameter1) > 0
                           -- change the Parameter 1 value
                           UPDATETEXT Subscriptions.Parameters
                                  @PARAMptrval
                                  @PARAM1Pos
                                  4
                                  @parameter1

       -- run the job
       exec msdb..sp_start_job @job_name = @scheduleID


       -- this give the report server time to execute the job.
       -- there is probably a better way to do this, so let me know if you know it ...
       WAITFOR DELAY '00:00:10'

       -- now change everything back so you can run this again

              -- set the start position for the TO Address
              SELECT @TOpos = patindex('%' + @TO + '%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
              SELECT @length = len(@TO)

        IF @length > 0
                     -- replace the addresses with the original |TO|
                     UPDATETEXT Subscriptions.ExtensionSettings
                           @ptrval
                           @TOpos
                           @length
                           '|EmailTO|'

              -- set the start position for the TO Address
              SELECT @CCpos = patindex('%' + @CC + '%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
              SELECT @length = len(@CC)

        IF @length > 0
                     -- replace the addresses with the original |CC|
                     UPDATETEXT Subscriptions.ExtensionSettings
                           @ptrval
                           @CCpos
                           @length
                           '|CarbonCopy|'

              -- set the start position for the TO Address
              SELECT @BCCpos = patindex('%' + @BCC + '%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
              SELECT @length = len(@BCC)

        IF @length > 0
                     -- replace the addresses with the original |BC|
                     UPDATETEXT Subscriptions.ExtensionSettings
                           @ptrval
                           @BCCpos
                           @length
                           '|BlindCopy|'

              -- set the start position for the REPLY TO Address
              SELECT @RTpos = patindex('%' + @ReplyTO + '%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
              SELECT @length = len(@ReplyTO)

        IF @length > 0
                     -- replace the addresses with the original |RT|
                     UPDATETEXT Subscriptions.ExtensionSettings
                           @ptrval
                           @RTpos
                           @length
                           '|ReplyTo|'

              -- set the start position for the BODY Text
              SELECT @BODYpos = patindex('%' + @BODY + '%', ExtensionSettings) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
             
              SELECT @length = len(@BODY)

        IF @length > 0
                     -- replace the addresses with the original |BD|
                     UPDATETEXT Subscriptions.ExtensionSettings
                           @ptrval
                           @BODYpos
                           @length
                           '|Comment|'

              -- set the start position for the Parameter
              SELECT @PARAM1Pos = patindex('%' + @parameter1 + '%', Parameters) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
      
              SELECT @length = len(@param1)

        IF @length > 0
                     -- replace the addresses with the original |P1|
                     UPDATETEXT Subscriptions.Parameters
                           @PARAMptrval
                           @PARAM1Pos
                           @length
                           '|Parameter1|'

You’ll notice the name of the SP ends with ‘1_Param’.  To add more use the following code in the relevant part of the SP:
              -- set the start position for the Parameter 2
              SELECT @PARAM2Pos = patindex('%|Parameter2|%', Parameters) - 1
              FROM Subscriptions WHERE SubscriptionID = @subscriptionID
      

             IF isnull(@PARAM2Pos, '') <> '' and @PARAM2Pos > 0 and len(@parameter2) > 0
                           -- change the Parameter 1 value
                           UPDATETEXT Subscriptions.Parameters
                                  @PARAMptrval
                                  @PARAM2Pos
                                  4
                                  @parameter2

                SELECT @length = len(@param2)
         IF @length > 0
                     -- replace the addresses with the original |P2|
                     UPDATETEXT Subscriptions.Parameters
                           @PARAMptrval
                           @PARAM1Pos
                           @length
                           '|Parameter2|'


It would be prudent to create a few versions of this SP with carrying numbers of parameters to meet all your needs.
The next SP is the one that runs and controls your new data-driven subscription capabilities.

declare @ReportParameter varchar(50)
declare @email_address varchar(255)
declare cur_cursor cursor for (select report_parameter,email_address

                                          from server.[database].[dbo].[tablewith data])
open cur_cursor
Fetch Next from cur_cursor into @ReportParameter, @email_address
While @@fetch_status = 0
begin

exec data_driven_subscription_1_Param
@scheduleID = 'FE98A2D4-4430-4D5C-806C-717470CB1332',
@TO = @email_address,
@CC = '',
@BCC = 'AdministrativeEmail@CompanyName.com',
@ReplyTO = 'RelevantTeam@YourCompany.com',
@BODY = 'Hello testing 123',
@param1 = @ReportParameter

Fetch Next from cur_cursor into @ReportParameter, @email_address
end
close cur_cursor
deallocate cur_cursor

This cursor uses a table directly, but you can create any complicated query you wish to determinethe parameters and emails, just as you would with a normal data-driven subscription.
Notice the @scheduleID – this is the scheduleID you collected after you saved the standard report subscription.

The above code can either be turned into an SP, or placed directly into a SQL Agent Job.

Once you have an Agent job to run this procedure you can schedule it when and as required.

Any questions please don’t hesitate to contact me.

24 comments:

  1. What I have been looking for well done

    ReplyDelete
  2. Hi Geoff
    This version of the process is the closest to actually working that I've found! I managed to process half of my emails before getting a sharepoint osws timer related fault. The routine has failed to run ever since. Have you come across any similar problems?

    ReplyDelete
    Replies
    1. Hi Squeaks. I'm not familiar with the Sharepoint aspect of your problem. Perhaps there is a timeout on the report or in Sharepoint that you need to extend. Does the report take a while to process, or does it run pretty smoothly?

      Check your SQL queries to look for anything that takes more than 60 seconds might be a good starting point.

      Sorry I can't be more help - but we don't use Sharepoint.

      Geoff

      Delete
    2. Also - try re-starting SSRS service on the server.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. I was wondering how the SP could pick up on multi-value parameters...

    ReplyDelete
  5. Geoff, I'm debugging this SP, but it looks like it had never been run correctly. For example, this code will never run correctly as it tailored to use 4 character |TO| instead of 9 character |EmailTo|...

    -- set the start position for the TO Address
    SELECT @TOpos = patindex('%|EmailTO|%', ExtensionSettings) - 1
    FROM Subscriptions WHERE SubscriptionID = @subscriptionID


    IF isnull(@TOpos, '') <> '' and @TOpos > 0 and len(@To) > 0
    -- change the TO address
    UPDATETEXT Subscriptions.ExtensionSettings
    @ptrval
    @TOpos
    4
    @To

    ReplyDelete
  6. Hi Julia, Although it has been a couple of years since I used this code, I did so quite successfully.
    I no longer have access to that server (different company), but you could just change the "|EmailTo|" to "|To|". I may have done this originally, but i can't remember.

    Sometimes I have to edit before posting here for various reasons, so it could be that I changed that bit.

    Thanks

    ReplyDelete
    Replies
    1. Anyone get this to work? I'm getting The following setting is not valid: "Hello testing subscription 123" after I amended the character lengths i.e. Changed |EmailTo| from 4 to 9

      Delete
    2. https://youtu.be/Ag0SJQQ7HrU

      Delete
    3. Hi,

      Try shortening 'Hello testing subscription 123' to 'testing' for now. It's unrelated to |Email| variable

      Delete
  7. Cool! Thanks :)

    ReplyDelete
  8. Do you have anything that will split a pdf report in different files based on parameters?

    ReplyDelete
  9. Yes, SQL-RD has a feature that does that. Check out christiansteven.com

    ReplyDelete
  10. Hi Geoff,

    I have this working, only issue is I have an intermittent fault, where it seems to send the correct amount of emails but it seems to miss some out but duplicates some by the same amount, i.e. send 3 of the same out but miss 2. Could increasing the wait delay time stop this issue? Any ideas? Thanks Charlie

    ReplyDelete
    Replies
    1. Not encountered that one. First thing would be the delay I agree. try being on the safe side and doubling or tripling it to 20-30 seconds so you can rule it out completely.

      Let me know how you get on.

      Delete
    2. Hi, I am having the exact same issue as the Anonymous post. Did increasing the wait time delay resolve the issue for you.

      Delete
  11. This worked awesome - for anyone out there who needs some tips, you definitely have to change either the character length of the place holder strings to 4 (ie replace |ReplyTo| with |To|) or you need to replace the number of characters its searching for

    also if you leave a line untouched, ie leaving |CarbonCopy| as is, it'll fail because that isn't a legit email format

    thanks!!

    ReplyDelete
  12. Job '8ABB5F48-7D6C-4D44-9126-123456896C' started successfully.
    Msg 7135, Level 16, State 2, Procedure data_driven_subscription_1_Param_Test, Line 73
    Deletion length 8 is not in the range of available text, ntext, or image data.
    The statement has been terminated.


    How I can resolve this

    ReplyDelete
    Replies
    1. I am also having the same error.

      Can anyone please help?

      Delete
    2. This comment has been removed by the author.

      Delete
  13. I know this is old, but still wanted to share.

    This could be due to the parameters data type in the stored procedure/query not being the same as the parameter in your report. I had the same issue, but modified the data type of the query's data type to match the parameter in the rdl file and it ended up working out fine.

    ReplyDelete
  14. I did something similar. My problem is the user updating the Extension & Parameter settings and then calling sp_start_job.

    UPDATE ReportServer.dbo.Subscriptions
    SET Parameters =
    (SELECT 'print_customer_nameTruejob_cut_sheet_id'
    + (SELECT CONVERT(NVARCHAR(6), @job_cut_sheet_id) )
    + '')
    , ExtensionSettings =
    (SELECT 'TOcwire@company.comIncludeReportTrueRenderFormatPDFSubject'
    + (SELECT @SubjectBody)
    + 'Comment'
    + (SELECT @SubjectBody)
    +'IncludeLinkFalsePriorityNORMAL')
    WHERE ReportServer.dbo.Subscriptions.SubscriptionId = '45559D89-A0D3-456D-A0DD-6E6072FF34AB'

    DECLARE @ScheduleId as NVARCHAR(50)
    SET @ScheduleId = 'CB8EAD1A-044A-4D8D-B667-7326ECE2275B'
    EXEC msdb.dbo.sp_start_job @ScheduleId

    ReplyDelete