Pause, Resume and Run a Reporting Services SharePoint Shared Schedule

I’ve recently re-entered the world of SharePoint Reporting Services integration and needed a way to run a shared schedule from a SQL job.

The scenario is this – We have a job that runs in the morning to populate the data warehouse, after the data warehouse it populates the cubes. What I want to happen next is for a number of reports to run against the cubes to warm up the SSAS cache and wake up reporting services ready for the business users.

So I have created the stored proc below to do just that – to make it a bit more useful I have also built in the ability to enable or disable a shared schedule. That way if the morning load fails we can programmatically disable the daily reports so they don’t go out with missing data.

Here’s the sproc….


USE ReportServer
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_Control_SharedSchedules]
(
@Schedule varchar(200)
,@Action varchar(8)
)
AS
BEGIN
/*
============================================================
= usp_Control_SharedSchedules 'Runs After Cube Processing Complete', 'Enable'
============================================================
= Description
= ------------------
= Author : Dan Wakefield
= Created Date : 1 Aug 2014
=
= History
= ------------------
= Created to allow us to kick off a set of reports to warm the cache
= after the cubes have finished processing.
=
= Can also be used to disable a schedule if the load fails and enable it again after it succeeds.
=
= Input
= --------
= @ScheduleName = Name of shared schedule e.g. 'Daily 10am'
= @Action = What do you want to happen? Disable, Enable or Run
(enable means it will run next time it reaches its usual scheduled time, run means run now)
=
= Return
= ----------
= RS
============================================================
*/
SET DATEFIRST 1
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

/*
===============================================================================================
= Declarations
===============================================================================================
*/

DECLARE @job_name VARCHAR(128)

SELECT @job_name = ScheduleID
FROM Schedule
WHERE Name = @Schedule

/*
===============================================================================================
= Processing Code
===============================================================================================
*/

IF @Action = 'Run'
BEGIN
-- Run a shared schedule
-- This will kick off all reports that are tied to the named shared schedule
EXEC msdb.dbo.sp_start_job @job_name
END

IF @Action = 'Disable'
BEGIN
-- Disable schedule
-- No reportts tied to this schedule will run until re-enabled
UPDATE MSDB.dbo.sysjobs
SET Enabled = 0
WHERE Name = @job_name;
END

IF @Action = 'Enable'
BEGIN
-- Enable schedule
-- All reports tied to this schedule will run next time the scheduled time arrives
UPDATE MSDB.dbo.sysjobs
SET Enabled = 1
WHERE Name = @job_name;
END

END

Comparing strings with trailing spaces

select top 1 * from database.dbo.table WHERE 1=0 — returns no results

— makes sense

select top 1 * from database.dbo.table WHERE 1=1 — returns 1 row

— yep

select top 1 * from database.dbo.table WHERE ‘frog’ = ‘frog’ — returns 1 row

— cool

select top 1 * from database.dbo.table WHERE ‘frog’ = ‘frog  ‘ — returns 1 row

— eh!!?

Apparently it’s by design – http://support.microsoft.com/kb/316626

I didn’t know that!

Splitting delimited strings in SQL

This is an issue I come back to a couple of times a year and always forget how I did it last so here’s the info…

I recently found a great blog that compares all the different methods of splitting delimited strings in SQL Server (there are lots!), interestingly it isn’t always the same solution that is the fastest – It depends on the number of elements in your strings…

http://www.sqlservercentral.com/articles/Tally+Table/72993/

As I’m never expecting more than 30 elements in my strings I’ve settled on the XML method – I found a good example of it here http://www.sqlservercentral.com/articles/XML/66932/

However this wasn’t working for me as I have special characters in my strings such as * and / which are illegal characters in XML – To get around this I had a chat with our DBA and he suggested adding "<![CDATA[" to act as effectively a text qualifier. This works great, see the code below…

Apologies for the dodgy apostrophes in the code below – I’m working on it!

Since playing with this a bit I ran into a problem with strange ASCII characters in my strings which was causing it to fail – The trick was to find out what the character was (in my case it was a ‘record separator’) and the remove it using REPLACE and CHAR() – Mine was a CHAR(30).

 

CREATE FUNCTION [dbo].[ufnSplitXml]  
   (@List nvarchar(max), @Delim char(1)= ',')  
RETURNS @Values TABLE (Item nvarchar(100))AS  
  
--  This function accepts a parameter @List containing the string to be split eg 'abc,def,ghi'
--  The default delimiter is comma but you can specify others if required.
--  Use a cross apply to join to this table valued function.
  
  BEGIN 
  
    DECLARE @XML XML
    
    IF Right(@List,LEN(@Delim)) = @Delim
    BEGIN
          SET @List = LEFT(@List,LEN(@List) - Len(@Delim))
    END

    SET @xml = N'<t><![CDATA[' + REPLACE(@List,@Delim,']]></t><t><![CDATA[') + ']]></t>'  

    INSERT INTO @Values
    SELECT  r.value('.','varchar(MAX)') as Item  
    FROM  @xml.nodes('/t') as records(r)
 
  RETURN  
  END 

Batching SQL Server Updates

We sometime have to apply updates to very large tables of data, it’s usually wise to do this in batches.

Here’s an example of an update being applied in 100,000 row batches – The raiserror command puts a message in the Messages window to tell us which batch has just been completed.


DECLARE @i INT = 0,
@j INT,
@msg VARCHAR(100)

SELECT @j = MAX(HouseholdKey) FROM DimHousehold

WHILE @i <= @j
BEGIN
BEGIN TRAN
UPDATE dh
SET dh.AvgDriveTime = AvgDeliveryTime
FROM DimHousehold dh
INNER JOIN #OrderData fo
ON dh.HouseholdKey = fo.HouseholdKey
AND dh.DeliveryType = ‘Delivery’
WHERE dh.HouseholdKey BETWEEN @i AND @i+99999
COMMIT TRAN

CHECKPOINT

SELECT @msg = CAST(@i AS VARCHAR(20)) + ‘ – ‘ + CAST(@i+99999 AS VARCHAR(20)) + ‘ household keys updated’
RAISERROR(@msg,1,1) WITH NOWAIT
SET @i = @i + 100000
END


Renaming / moving projects and solutions in BIDS

Something that has annoyed me plenty in the past but I’ve never bothered to figure it out properly, well it happened to me again today so I though I should put it to bed once and for all.

Lets say you create an SSIS project in BIDS, play with it for a bit, then decide you need another project in that solution – So you add your project and find your solution is named after your first project.

Renaming the solution in BIDS is easy – The problem is if you look in windows explorer the main folder for your solution is also named after your first project – You want to change this to match the new name of you solution.

So you rename the folder in windows explorer and suddenly you can’t open your projects in BIDS anymore – It doesnt know where they are any more.

The trick is to edit the .sln file for the solution in notepad – You will need to change the file to not be read only first (I also ran notepad as admin for good measure).

Once in notepad you will see a couple of places where you need to change the path to match your new folder name (see example below)

Make the changes to reflect what is now the case (ie what you see in windows explorer) – Save the file, then re-open in BIDS and all should be good with the world!
Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
Project(“{D183A3D8-5FD8-494B-B014-37F57B35E655}”) = “xLoadLoad”, “xLoadLoad\xLoadLoad.dtproj”, “{84171A3D-08EF-4E73-B60A-7EBA421F58EE}”
EndProject
Project(“{D183A3D8-5FD8-494B-B014-37F57B35E655}”) = “xDWHLoad”, “xDWHLoad\xDWHLoad.dtproj”, “{90639253-2455-4D72-9C2A-269F6C259E2A}”
EndProject
Project(“{D183A3D8-5FD8-494B-B014-37F57B35E655}”) = “xStageLoad”, “xStageLoad\xStageLoad.dtproj”, “{75B18651-AEA6-41B8-BE5B-7B351FEBD1DC}”
EndProject
Global
GlobalSection(TeamFoundationVersionControl) = preSolution
SccNumberOfProjects = 4
SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
SccTeamFoundationServer = xxx
SccLocalPath0 = .
SccProjectUniqueName1 = xDWHLoad\\xDWHLoad.dtproj
SccProjectName1 = xDWHLoad
SccLocalPath1 = xDWHLoad
SccProjectUniqueName2 = xStageLoad\\xStageLoad.dtproj
SccProjectName2 = xStageLoad
SccLocalPath2 = xStageLoad
SccProjectUniqueName3 = xLoadLoad\\xLoadLoad.dtproj
SccProjectName3 = xLoadLoad
SccLocalPath3 = xLoadLoad
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Development|Default = Development|Default
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{84171A3D-08EF-4E73-B60A-7EBA421F58EE}.Development|Default.ActiveCfg = Development
{84171A3D-08EF-4E73-B60A-7EBA421F58EE}.Development|Default.Build.0 = Development
{90639253-2455-4D72-9C2A-269F6C259E2A}.Development|Default.ActiveCfg = Development
{90639253-2455-4D72-9C2A-269F6C259E2A}.Development|Default.Build.0 = Development
{75B18651-AEA6-41B8-BE5B-7B351FEBD1DC}.Development|Default.ActiveCfg = Development
{75B18651-AEA6-41B8-BE5B-7B351FEBD1DC}.Development|Default.Build.0 = Development
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

No credits for this one as I worked it out myself 😀

SSRS – Passing Multi-Value parameters between reports

Not posted for a while but this one was worth remembering…

In order to pass multi-value parameters from one report to another you need to use a combination of the following:

StrToSet – MDX function that converts a string to an MDX set

Split – An SSRS expression function that create an array object from a comma seperated string

Join – An SSRS expression function that create a comma seperated string (in this case from a set of parameter values)

In both reports you need a parameter that is set to accept multiple values – Lets call it prmMulti (in both reports, although the name could be different in each report)

The parameter should have values that look like MDX members ie [dimension].[attribute].&[member] – You can use currentmember.uniquename for this.

In the parent report go to the text box you want to click to drill to the child report and under navigation/action select prmMulti and enter an expression for the value to pass down as follows:

=Split(Join(Parameters!prmMulti.Value,”,”),”,”)

The join function creates a comma seperated list from all the selected parameter values and the split function then converts this into an array object – This is what gets passed to the child report.

Thanks go to Erika for this one – http://erikasblog.datainspirations.com/2010/01/21/passing-multi-value-sql-parameters-to-an-mdx-query-in-reporting-services/

Self documenting database (SQL Server)

Like most developers (and in fact most sane people) I dont like doing documentation, however you can’t deny the value of it so I went about trying to see how it could be done in SQL Server without having to buy some software or have a prolonged fight with MS Word!

A quick google led me to the extended properties page of a columns properties (in management studio)

You can add all the extra meta data you require here specific to your project, in this example I’ve added 3 extended properties – Source, Frequency loaded and Owner.  I’ve then given these properties values for this particular column.

That meta data is then stored against the column’s id in a system table called sys.extended_properties, we can see this by running a quick select statement:

Now all we need to do is pivot this so we get one row with all the extended properties as columns and we can join onto the INFORMATION_SCHEMA.COLUMNS system view using the major and minor ids.  I have had to do a bit of jiggery pokery in order to not have to specify the names of the extended properties (we wont necessarily know what they are all the time!).  I dont have time to go into it in detail but have a look at the comments in the code below and it should hopefully make sense.

declare

 

@names asvarchar(100)

 

declare

 

@sql asnvarchar(max

 

 

— populate @names variable with the columns we want to include in our PIVOT statement

select

 

@names =coalesce(@names +‘, ‘,)+ name from

sys.extended_properties ep 

— build up the sql for the PIVOT statement (including the columns names captured above)

set

@sql =  

‘SELECT major_id, minor_id,

+ @names +‘into ##tmp

FROM

(SELECT name, value, major_id, minor_id

FROM sys.extended_properties

WHERE class = 1) AS SourceTable

PIVOT

(

max(Value)

FOR name IN (‘

 

 

+ @names +‘)) AS PivotTable’

 

 

— run the sql to save this data in ##tmp

exec

sp_executesql@sql

— Bring it all together by pulling the columns details from information_schema

— and then joining on the extra metadata gathered above

SELECT

TABLE_NAME

, COLUMN_NAME, COLUMN_DEFAULT,c

.IS_NULLABLE,case

whencast(DATA_TYPE ASvarchar(10))like‘%char%’  

thencast(DATA_TYPE ASvarchar(10))+‘(‘+replace(cast(CHARACTER_MAXIMUM_LENGTH ASvarchar(6)),‘-1’,‘max’)+‘)’ 

else data_type end,ep

.*FROM

INFORMATION_SCHEMA

.COLUMNS cINNER

JOIN

 

sys

.columns scON

OBJECT_ID

(c.TABLE_SCHEMA +‘.’+ c.TABLE_NAME)= sc.[object_id]

AND

c.COLUMN_NAME = sc.name

LEFT

OUTERJOIN

 

##tmp ep

ON

sc

.[object_id] = ep.major_idAND

sc.[column_id] = ep.minor_id

ORDER

BY

 

1

, 2; 

 

Create a comma separated string

I just used this again after 2 years so though I should flesh out the description a bit.

This is really useful if you need to quickly create a delimited string based on a GROUP BY statement.

The example below illustrates this using a simple set of sample data.

select 1 as id, ‘frog’ as name into #tmp
insert into #tmp (id, name) values (1,’toad’)
insert into #tmp (id, name) values (2,’cat’)
insert into #tmp (id, name) values (2,’dog’)

SELECT p1.Id, ( SELECT Name + ‘,’ FROM #tmp p2
WHERE p2.Id = p1.Id ORDER BY Name FOR XML PATH(”) ) AS list
FROM #tmp p1 GROUP BY Id ;