Tuesday, June 23, 2009
Reporting Services and SharePoint - Context Expired Exception
At one of our client locations, developers and users complained of issues in accessing reports within SharePoint. The error was not occurring consistently. We quickly decided that since it wasn't happening every time, it may be a web-front-end (WFE) issue (as there are three WFEs load-balanced together) . After thinking about this, we narrowed it down to one of the WFEs.
When trying to open a report or even looking at a data source, SharePoint would throw an exception: "Report Server has encountered a SharePoint error. ---> Microsoft.SharePoint.SPException: The context has expired and can no longer be used. (Exception from HRESULT: 0x80090317) " . The full error message is at the bottom of this post.
Solution
I tried the usual remedy steps such as IISRESET and rebooting the server. I then tried to repair the RS Add-In and even run the SharePoint Configuration Wizard. Nothing was working but I did notice in the logs that the timestamps were not the right time.
After several other (almost) drastic actions, I decided to fix the clock on the server to have the correct time. Voila! This resolved the context issue and everything started working. The clock was so much different than any client machine that the security context was assumed to be expired.
Sometimes a simple thing can cause major problems and sometimes major problems can be fixed by a simple solution.
Full Error Message
System.Web.Services.Protocols.SoapException: Report Server has encountered a SharePoint error. ---> Microsoft.ReportingServices.Diagnostics.Utilities.SharePointException: Report Server has encountered a SharePoint error. ---> Microsoft.SharePoint.SPException: The context has expired and can no longer be used. (Exception from HRESULT: 0x80090317) ---> System.Runtime.InteropServices.COMException: The context has expired and can no longer be used. (Exception from HRESULT: 0x80090317) --- End of inner exception stack trace --- at Microsoft.ReportingServices.WebServer.ReportingService2005Impl.GetDataSourceContents(String DataSource, DataSourceDefinition& Definition) at Microsoft.ReportingServices.WebServer.ReportingService2006.GetDataSourceContents(String DataSource, DataSourceDefinition& Definition)
Friday, June 05, 2009
PerformancePoint Connection issues
Case in point - when making identity changes to the App Pool, BE SURE TO RESET IIS or those changes aren't in effect. (Recycling the App Pool is not sufficient.)
For those not familiar, the easiest way to cycle IIS is to open a command prompt and enter IISRESET. It's a quick process, but as always, be sure to coordinate with anyone else that may be using web services at that time.
Tuesday, June 02, 2009
Coexistence of Report Viewer Versions 8.x and 9.x in SharePoint
<add verb="*" path="Reserved.ReportViewerWebControl.axd" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
This was fine. However, the SharePoint usage report page would not render because it requires the Version 8.0.0.0 HTTP Handler entry. The error message actually stated that that the line was missing. It wasn't missing, it was in there:
<add verb="*" path="Reserved.ReportViewerWebControl.axd" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
It came first in the HTTP Handler section and we quickly realized that the last entry for the same path wins the battle. There seems to be no way of having two HTTP Handlers for the same path (which make sense). So in our case, either the custom reporting solution was broken or the Site Usage reports would not work; this would not be acceptable for long.
I needed to find away to have both Version 8.0.0.0 and Version 9.0.0.0 work within the same web application. I tried various configuration settings and "hacks" in attempt to get it to work until finally I noticed something in the Reporting Services web config. There was some sort of an assembly redirect to tell IIS (ultimately) to use a different version of an assembly. It wasn't the assembly I was dealing with but I figured I may be able to use the same approach.Therefore, within the SharePoint web.config for port 80, I entered the following within the <runtime> <assembly binding> section under the <system.web> settings :
<dependentAssembly>
<assemblyIdentity name="Microsoft.ReportViewer.WebForms" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="8.0.0.0"
newVersion="9.0.0.0"/>
</dependentAssembly>
Essentially, this was telling SharePoint that "if you are looking for the 8.0.0.0 version, use the 9.0.0.0 instead". This allowed the Site Usage page to at least render! It rendered with all of the web parts however the web parts all contained the same error message. They were now looking for the 9.0.0.0 version of the Microsoft.ReportViewer.ProcessingObjectModel assembly which didn't exist. I looked in the GAC and there was only the Version 8.0.0.0 in there. So I figured this was probably deprecated with the latest Report Viewer updates for SharePoint.
So I decided to be tricky and use the same redirect for the Microsoft.ReportViewer.ProcessingObjectModel assembly but using the opposite version settings:
<dependentAssembly>
<assemblyIdentity name="Microsoft.ReportViewer.ProcessingObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="9.0.0.0"
newVersion="8.0.0.0"/>
</dependentAssembly>
This actually worked! The Site Usage reports rendered and the development team's custom reporting interfaces were still functioning. We thought we were going to have to apply MOSS 2007 SP2 and/or SQL Server SP3 and worse case open a case ticket with Microsoft Support - not anymore!
Thursday, May 14, 2009
SSAS and Dynamic Excel Reports
Excel can be a very powerful tool for allowing power users to analyze data in cubes. However it is also possible to create dynamic reports using Excel. This can be accomplished using Excel Cube functions. Cube functions allow you to create the same type of functionality achieved in a pivot table but in a individual cell. Then utilizing other Excel functions it is possible to parameterize the report making it dynamic.
For this example, I will use one of the Adventures Works Cubes. The first step is to create a pivot table that contains the information we want. For this example I created a comparison of Internet Gross Profit sales for current month, prior month and the same month a year ago. This data is further divided by primary sales territory.
Personally I found that using the Cube Functions in the formula editor to be complicated. Thankfully there is a menu option in Excel 2007 that can help. It is called ‘Convert to Formulas’ and is found in the PivotTable Tools –> OLAP Tools menu.
Once this table has been converted we can analyze a few key cells to understand what is going on. The first is A3 where the measure is located. (All cell references relate to the image of the pivot table above.) This cell now contains the formula:
=CUBEMEMBER("AdventureWorksDW",
"[Measures].[Internet Gross Profit]")
The function CUBEMEMBER allows you to select the measure. If you edit the text you can select other measures, just delete all the text inside quote after this ‘[Measures].[’ and Excel will offer other options.
The second cell we want to inspect is B8 (or any other value cell.)
=CUBEVALUE("AdventureWorksDW",$A$3,$A8,B$7)
You can see that this cell just contains the name of the data source and references to other cells. One thing to note is that not all cells that were in the pivot table are used by the cube functions so they can be deleted. Here is my cleaned up table.
The next step is to parameterize the dates reference in my converted table so that we can make the report dynamic. Evaluating the cell D4 will help us do that.
=CUBEMEMBER("AdventureWorksDW","[Date].[Calendar].[Month].&[2004]&[5]")
You can see that the year and month number are used, however this will vary depending on how the time dimension in your cube is setup. The first step is to place the date to drive the report into cell B1. Then change the formulas in B4, C4, and D4 to use this date.
I replaced the formulas in cells B4 and D4 with the ones below.
B4: =CUBEMEMBER("AdventureWorksDW", CONCATENATE("[Date].[Calendar].[Month].&[",YEAR(B1)-1,"]&[",MONTH(B1),"]"))
D4: =CUBEMEMBER("AdventureWorksDW",
CONCATENATE("[Date].[Calendar].[Month].&[",YEAR(B1),"]&[",MONTH(B1),"]"))
The only difference is we subtract 1 year from cell B4. Cell C4 is a little more difficult because when the current month is January the prior month is December of the prior year. If it is January the we need to add an if statement to see if we need to subtract 1 form the the year and set the month to 12. If not we can just subtract 1 from the month number.
C4: =CUBEMEMBER("AdventureWorksDW", CONCATENATE("[Date].[Calendar].[Month].&[",IF(MONTH(B1)=1,YEAR(B1)-1,YEAR(B1)),"]&[",IF(MONTH(B1)=1,12,MONTH(B1)-1),"]"))
The final spreadsheet now looks like the image below but now the data in the table, which is pulled from the cube can be updated by changing the date in cell B2.
This is a straightforward example but could easily be expanded to handle more complex cases. Graph data can also be updated using the same methodology. So it is possible to see that this now gives you a complete report authoring environment that you can give to Excel power users and allow them to build their own reports from the cubes your organization has built.
Tuesday, May 12, 2009
Connecting to DB2 on AS/400 via OLE DB
To connect to a DB2 database via OLE DB, you’ll need the Microsoft OLEDB Provider for DB2 (IBM also has one), which is part of the Microsoft SQL Server 2008 Feature Pack.
After installing the provider, there are several ways you can test connectivity: creating a UDL file, using the “Data Access Tool” that ships with the Microsoft provider, or creating an OLE DB Connection in BIDS to name a few. Below is a screen shot of the test connection’s property grid. As you might expect, I’ve replaced the values of user id, IP address, schema, etc., with fake ones. Properties to note are:
- DBMS Platform: in my case, this is DB2\AS400
- Default Schema: same as the library name in DB2. Ex., the library name in the following query is “LIBNAME”: “SELECT * FROM LIBNAME.FILENAME”
- Package Collection: same as above
- Initial Catalog: Your AS/400 DBA should be able to provide this. Or, if you have access to a linked server, expand it in SSMS and you should see the catalog name.
At this point, my test connections were unsuccessful. But the Microsoft OLEDB Provider for DB2 ships with a handy “SNA Trace Utility” that revealed more information:
|00000f50.000008b0 DDM 001B1153 E4E2C5D9 40C5E7C9 E340C4C5 D5C9C5C4 40C1C3C3 C5E2E2
|00000f50.000008b0 DRDA AR message: Name: RDBATHRM, Severity: Error, Diagnostic: USER EXIT DENIED ACCESS, Database: S1033BC1
Notice the DRDA “DENIED ACCESS” message. Interestingly, the Microsoft OLEDB Provider for DB2 uses the DRDA protocol. Below is a screenshot from a 5250 session with the AS/400. Notice the configuration setting “DDM / DRDA request access”, which is configured to reject all DDM / DRDA connections. Changing this value to “*OBJAUT” resolved the issue. For more information, see the CHGNETA Command Description and look for DDMACC (DDM / DRDA request access). See also Microsoft KB article 246714.
Friday, May 01, 2009
Non-Standard uses for SSIS Package Configurations
SQL Server Integrations Services Package Configurations: Much more than dynamic connections
Package Configurations in SSIS allow you to dynamically change object property values such as a connection’s ConnectString simply by changing a row in a SSIS Package Configuration table or an XML Configuration File. They make your SSIS solution much more flexible in many ways and while connection strings are tops on the package configuration hit list, this blog entry discusses additional ways one can take advantage of SSIS Package Configurations.
Why do we need Package Configurations?
Think about how this feature can be used. As your SSIS packages moves up the food chain from Development to Test and eventually to Production, you may need to change many package attributes along the way. Maybe while in TEST, your packages extracted sample data from a partial copy of the production data. Maybe while in TEST your package wrote to a TEST destination (SQL Instance), or FTP-ed files to a ftp development folder from an FTP TEST Site. Finally, maybe while in TEST your solution emailed a group of developers if there were any package failures. What if you thoroughly tested your SSIS solution in this environment and you are ready to move your solution into production?
Scary memories of DTS and package promotion / deployment
If you worked with SQL Server 2000’s DTS, one of the only ways to do deploy a package to a new server was while in the DTS Package Editor issue a Package à Save As and change the SQL Instance where the Package physically resides. Once this was done, in many cases you had to then manually change all of the connections to point to the correct product-mode sources and destinations. This was a painstakingly manual process. In many cases, many other package attributes needed to change in addition to sources and destinations. Enter SSIS Package Configurations. They allow you to modify rows in a Package Configuration Table or XML configuration file to achieve the same end-result.
How Package Configurations Work
While there are several types of Package Configurations in this example we have chosen to store our package configuration / dynamic values in a SQL Server table. Further, we have chosen to create a database called SSIS that contains our package configuration tables along with some other tables and stored procedures used by our auditing subsystem. SSIS allows you to define a Package Configuration Environment Variable, and we use this to store a connect string to the SQL Instance that houses this SSIS database. Our environment variable was called SSISPACKAGECONFIGURATIONS. We maintain multiple copies of the PackageConfiguration table each with different values to support the different environment. For example – we have one package configuration table that is used in my local development environment to point to my local SQL 2005 Developers Edition, while another was created for our shared test SQL Instance and yet another for our official production SQL Instance.
Both standard and non-standard Package Configuration Examples
SSIS package Configurations are easily created in Business Intelligence Development Studio. (SSIS -> Package Configurations)
Let’s look at our package configuration categories and provide a typical Business Intelligence Development Studio (BIDS) Solution that utilizes them:
1. Source and Destination Connections and directories
SSIS Package configurations can be used to hold the connection strings for our connections to our SSIS, Stage and SalesDW databases which are databases used by our solution. In this solution, we are extracting monthly sales data from a FTP Site, so we can dynamically store the directories used to support this operation along with the FTP security credentials.
a. SSIS, Stage and SalesDW OLEDB Connections
These represent standard OLEDB connections used by packages as either a source or destination. This may the most common use for package configurations. For these package configurations we choose to designate the ConnectString property of these connections making it the dynamic property we can easily change.
b. FTP Working Directory
Our solution uses an ftp .bat file and ftp scripts to drive the FTP get and put commands. We’ve found this gave us more flexibility than using the SSIS FTP task. This package variable designates the directory that houses the .bat and script files used by the FTP command. We had one location while our solution was in development, but in production our servers were clustered so we couldn’t reference a server’s local drive.
c. FTP Local Directory, FTP Remote Directory
Once connected to the FTP site you can change your local and remote directories using the lcd the cd commands. The local directory setting determined where files resided after they were gotten using the FTP mget command. As packages moved from development to test to production these did need to change so creating package variables whose values were package configuration items saved us on package maintenance.
d. SSIS Package Directory
Initially this was set to the ‘working directory’ while we were working with the SSIS Solution in the Business Intelligence Development Studio. (In our case, the working directory was a SourceSafe local working directory setting) Once the packages were deployed to a production server, this changed in the package configuration table present in the production server to the actual deployment path on the production server. (for our solution, we stored our package files in the file system as opposed to in SQL Server)
2. FTP Attributes
We’ve found that while in development mode we relied on a test FTP instance, but once our solution was deployed to production we were asked to use the official production site. All security credentials changed; therefore, storing these as package variables whose values were package configuration items saved us on package maintenance.
a. FTP Site Name, FTP Login, FTP Password
The FTP Security credentials were stored in package variables as package configuration items. This allows us to easily make changes without having to modify our SSIS Solution. Instead, we modified rows in the package configuration table.
b. FileToExtract
Our FTP site supported the use file wildcards (% matched any single character) and we were able designate the file to extract making the Year and Month (YYMM) portion of the file match any year and month. This way no matter what files were dropped to our FTP Site, our solution would auto-discover them and then extract and load them.
c. ParentTraceMode
We’ve found that while in development mode we actually wanted to the FTP results. What files did FTP get or what files did FTP move/rename. At first, this wasn’t possible because the FTP window appeared and disappeared too quickly since we were working with very small files. We decided to create a package variable called ParentTraceMode whose value is obtained from the calling parent using the Parent Package Configuration. If this value is set to 1, SSIS Precedence Constraints force the execution of a trace mode FTP command that simply does not provide the ending FTP quit statement. Sounds simple, but without the quit command the FTP window stays displayed for us to visually inspect. I found this trace flag also useful to direct the flow of the package to enable other tasks such as Script Tasks the display useful information via message boxes. Also, our FTP processing normally cleans up after itself by removing the FTP Script files that were just used. Good thing because there’s some sensitive security credentials in this file! With trace mode enable these script files remain in the FTP Working Directory for inspection.
3. Email Recipients
While in development our email recipients were hard-coded to be our development team members. As we got close production deployment, we realized there were many more interested parties; therefore, this list was changed to a package variable whose value was a package configuration.
a. EmailRecipients
Stores a semi-colon separated list of email recipients. This package variable is then passed to our stored procedure that sends out an SMTP Email notification.
4. Auditing Related Configurations
The auditing subsystem uses package configurations extensively. Below one package configuration example:
a. ParentPgkID
Each package is assigned a unique identifier by the auditing subsystem. This is a parent package configuration (a package configuration value obtains from the calling or parent package) that allows track parent and child package execution hierarchy from our auditing tables. This has proven very useful since it is very common to have a master package whose job it is to execute all extract and load packages in the correct order – thereby producing an entire batch extract and run.
Wednesday, April 15, 2009
Sales for past six months using SQL Pivot Operator
A simple revenue report needed to be generated that displays the summarized revenue dollars for each customer from a SalesDetail table. The YYMM column that holds the year and month ('0904' = April, 2009 ...). This YYMM column show be transposed and displayed as a column header in the report rather than have it display in each row of data.
Background
Each month, we get revenue new data that is appended to the SalesDetails table. At first, we simply modified the SQL Code that is generated the report data to include the new month's data. Each month our report got a bit wider, but it was nice to see all of our past data and new total revenue. Our client decided that they would rather have a no-maintenance solution that simply reports on the most recent 6 months (They didn't need to go back any further). This way they didn't have to make any report changes from month-to-month.
Using PIVOT Operator, we had the report working by hard-coding each YYMM value, but we are looking for a solution without any hard-coding. Here is the sample of SQL using hard-coded YYMM values
SELECT
CustomerNumber,
CustomerName,
[0810] as 'Oct 2008 Revenue',
[0811] as 'Nov 2008 Revenue',
[0812] as 'Dec 2008 Revenue',
[0901] as 'Jan 2009 Revenue',
[0902] as 'Feb 2009 Revenue',
[0903] as 'Mar 2009 Revenue',
[0904] as 'Apr 2009 Revenue',
[0810] + [0811] + [0812] + [0901] + [0902] + [0903] + [0904] as 'Total Revenue'
FROM
(SELECT
CustomerNumber, CustomerName, Revenue, YYMM
FROM SalesDetails
) SD
PIVOT
( SUM(Revenue) FOR YYMM IN ([0810], [0811], [0812], [0901], [0902], [0903], [0904])) as pvt
ORDER BY
CustomerNumber, CustomerName
Solution
This problem can be solved using PIVOT operator and without Hard-coding the YYMM values, but getting the column names as mentioned in the sample SQL above could be difficult.
PIVOT Operator works on fixed number of attribute values. Hence the number of values cannot be dynamic. Hence I tried work around this issue.
Here is SQL to create the Table and load sample data
CREATE TABLE dbo.SalesDetails
(
YYMM CHAR(4) NULL,
CustomerName VARCHAR(64) NULL,
CustomerNumber VARCHAR(32) NULL,
Revenue MONEY NULL
)
GO
----- Sept 2008 Sales
INSERT dbo.SalesDetails VALUES ('0809', 'Sears' ,'11111', $34.07)
INSERT dbo.SalesDetails VALUES ('0809', 'Sears' ,'11111', $24.07)
INSERT dbo.SalesDetails VALUES ('0809', 'Kmart' ,'22222', $41.11)
INSERT dbo.SalesDetails VALUES ('0809', 'Kmart' ,'22222', $1.78)
--- Oct 2008 Sales
INSERT dbo.SalesDetails VALUES ('0810', 'Sears' ,'11111', $34.99)
INSERT dbo.SalesDetails VALUES ('0810', 'Sears' ,'11111', $124.00)
INSERT dbo.SalesDetails VALUES ('0810', 'Sears' ,'11111', $11.11)
INSERT dbo.SalesDetails VALUES ('0810', 'Kmart' ,'22222', $61.78)
--- Nov 2008 Sales
INSERT dbo.SalesDetails VALUES ('0811', 'Sears' ,'11111', $84.39)
INSERT dbo.SalesDetails VALUES ('0811', 'Kmart' ,'22222', $124.00)
INSERT dbo.SalesDetails VALUES ('0811', 'Kmart' ,'22222', $11.11)
INSERT dbo.SalesDetails VALUES ('0811', 'Kmart' ,'22222', $61.78)
--- Dec 2008 Sales
INSERT dbo.SalesDetails VALUES ('0812', 'Sears' ,'11111', $2.99)
INSERT dbo.SalesDetails VALUES ('0812', 'Sears' ,'11111', $41.81)
INSERT dbo.SalesDetails VALUES ('0812', 'Kmart' ,'22222', $283.23)
--- Jan 2009 Sales
INSERT dbo.SalesDetails VALUES ('0901', 'Sears' ,'11111', $2.99)
INSERT dbo.SalesDetails VALUES ('0901', 'Sears' ,'11111', $41.81)
INSERT dbo.SalesDetails VALUES ('0901', 'Kmart' ,'22222', $43.44)
INSERT dbo.SalesDetails VALUES ('0901', 'Kmart' ,'22222', $10.34)
INSERT dbo.SalesDetails VALUES ('0901', 'Kmart' ,'22222', $29.00)
INSERT dbo.SalesDetails VALUES ('0901', 'Kmart' ,'22222', $111.34)
--- Feb 2009 Sales
INSERT dbo.SalesDetails VALUES ('0902', 'Sears' ,'11111', $2.99)
INSERT dbo.SalesDetails VALUES ('0902', 'Sears' ,'11111', $41.81)
INSERT dbo.SalesDetails VALUES ('0902', 'Kmart' ,'22222', $43.44)
INSERT dbo.SalesDetails VALUES ('0902', 'Kmart' ,'22222', $10.34)
INSERT dbo.SalesDetails VALUES ('0902', 'Kmart' ,'22222', $29.00)
INSERT dbo.SalesDetails VALUES ('0902', 'Kmart' ,'22222', $111.34)
--- Mar 2009 Sales
INSERT dbo.SalesDetails VALUES ('0903', 'Sears' ,'11111', $12.89)
INSERT dbo.SalesDetails VALUES ('0903', 'Sears' ,'11111', $21.81)
INSERT dbo.SalesDetails VALUES ('0903', 'Kmart' ,'22222', $33.40)
INSERT dbo.SalesDetails VALUES ('0903', 'Kmart' ,'22222', $110.94)
--- Apr 2009 Sales
INSERT dbo.SalesDetails VALUES ('0904', 'Sears' ,'11111', $14.89)
INSERT dbo.SalesDetails VALUES ('0904', 'Sears' ,'11111', $15.81)
INSERT dbo.SalesDetails VALUES ('0904', 'Kmart' ,'22222', $13.40)
INSERT dbo.SalesDetails VALUES ('0904', 'Kmart' ,'22222', $156.94)
Since PIVOT works on fixed set of Attributes, I used a CASE statement and passed parameters to segregate past 6 months data as [SIXTHMONTH], [FIFTHMONTH], [CURRENTMONTH] etc. I used this new fixed attributes in the PIVOT portion. Here is the solution
DECLARE @SixthLastMonth char(4)
DECLARE @FifthLastMonth char(4)
DECLARE @FourthLastMonth char(4)
DECLARE @ThirdLastMonth char(4)
DECLARE @SecondLastMonth char(4)
DECLARE @LastMonth char(4)
DECLARE @CurrentMonth char(4)
SELECT @SixthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -6, GETDATE()), 12), 1,4)
SELECT @FifthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -5, GETDATE()), 12), 1,4)
SELECT @FourthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -4, GETDATE()), 12), 1,4)
SELECT @ThirdLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -3, GETDATE()), 12), 1,4)
SELECT @SecondLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -2, GETDATE()), 12), 1,4)
SELECT @LastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -1, GETDATE()), 12), 1,4)
SELECT @CurrentMonth = SUBSTRING(CONVERT(VARCHAR, GETDATE(), 12), 1,4)
SELECT
CustomerNumber,
CustomerName,
ISNULL([SIXTHMONTH], 0.00) as 'Sixth Last Month Revenue',
ISNULL([FIFTHMONTH], 0.00) as 'Fifth Last Month Revenue',
ISNULL([FOURTHMONTH], 0.00) as 'Fourth Last Month Revenue',
ISNULL([THIRDMONTH], 0.00) as 'Third Last Month Revenue',
ISNULL([SECONDMONTH], 0.00) as 'Second Last Month Revenue',
ISNULL([LASTMONTH], 0.00) as 'Last Last Month Revenue',
ISNULL([CURRENTMONTH], 0.00) as 'Current Last Month Revenue',
ISNULL([SIXTHMONTH], 0.00) + ISNULL([FIFTHMONTH], 0.00)
+ ISNULL([FOURTHMONTH], 0.00) + ISNULL([THIRDMONTH], 0.00)
+ ISNULL([SECONDMONTH], 0.00) + ISNULL([LASTMONTH], 0.00)
+ ISNULL([CURRENTMONTH], 0.00) AS 'Total Revenue'
FROM
( SELECT
CustomerNumber,
CustomerName,
Revenue,
CASE YYMM
WHEN @SixthLastMonth THEN 'SIXTHMONTH'
WHEN @FifthLastMonth THEN 'FIFTHMONTH'
WHEN @FourthLastMonth THEN 'FOURTHMONTH'
WHEN @ThirdLastMonth THEN 'THIRDMONTH'
WHEN @SecondLastMonth THEN 'SECONDMONTH'
WHEN @LastMonth THEN 'LASTMONTH'
WHEN @CurrentMonth THEN 'CURRENTMONTH'
ELSE 'OTHER'
END [YYMM]
FROM SalesDetails
) SD
PIVOT
( SUM(Revenue) FOR YYMM IN ( [SIXTHMONTH], [FIFTHMONTH], [FOURTHMONTH], [THIRDMONTH], [SECONDMONTH], [LASTMONTH], [CURRENTMONTH])
) as pvt
ORDER BY
CustomerNumber, CustomerName
This solves the issue of Hard-coding the YYMM values. But the column names will be like “Sixth Last Month Revenue” instead of “October 2008 Revenue”. Well this can be solved by tweaking the headings expression in the SSRS reports.
Alternate Solutions
Using SSRS reports
One can use PIVOT reports to solve this problem by filtering out sales which is older than six months. The column headings expression need to be tweaked to show the values like “October 2008 Revenues” etc. Here is the Sample Screen shot
Using Dynamic SQL
Dynamic SQL can be used to solve this issue too. Here is the code.
declare @ColNameSixth char(40), @ColNameFifth char(40), @ColNameFourth char(40), @ColNameThird char(40), @ColNameSecond char(40), @ColNameLast char(40), @ColNameCurrent char(40)
select @ColNameSixth = datename(mm, dateadd(mm, -6, getdate())) + ' ' + cast(YEAR(dateadd(mm, -6, getdate())) as varchar) + ' Revenues'
select @ColNameFifth = datename(mm, dateadd(mm, -5, getdate())) + ' ' + cast(YEAR(dateadd(mm, -5, getdate())) as varchar) + ' Revenues'
select @ColNameFourth = datename(mm, dateadd(mm, -4, getdate())) + ' ' + cast(YEAR(dateadd(mm, -4, getdate())) as varchar) + ' Revenues'
select @ColNameThird = datename(mm, dateadd(mm, -3, getdate())) + ' ' + cast(YEAR(dateadd(mm, -3, getdate())) as varchar) + ' Revenues'
select @ColNameSecond = datename(mm, dateadd(mm, -2, getdate())) + ' ' + cast(YEAR(dateadd(mm, -2, getdate())) as varchar) + ' Revenues'
select @ColNameLast = datename(mm, dateadd(mm, -1, getdate())) + ' ' + cast(YEAR(dateadd(mm, -1, getdate())) as varchar) + ' Revenue'
select @ColNameCurrent = 'Current Revenues'
DECLARE @SixthLastMonth char(4), @FifthLastMonth char(4),@FourthLastMonth char(4),@ThirdLastMonth char(4),@SecondLastMonth char(4),@LastMonth char(4),@CurrentMonth char(4)
SELECT @SixthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -6, GETDATE()), 12), 1,4)
SELECT @FifthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -5, GETDATE()), 12), 1,4)
SELECT @FourthLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -4, GETDATE()), 12), 1,4)
SELECT @ThirdLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -3, GETDATE()), 12), 1,4)
SELECT @SecondLastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -2, GETDATE()), 12), 1,4)
SELECT @LastMonth = SUBSTRING(CONVERT(VARCHAR, DATEADD(mm, -1, GETDATE()), 12), 1,4)
SELECT @CurrentMonth = SUBSTRING(CONVERT(VARCHAR, GETDATE(), 12), 1,4)
DECLARE @SQL NVARCHAR(4000)
SELECT @SQL = '
SELECT
CustomerNumber,
CustomerName,
ISNULL([' + @SixthLastMonth + '], 0.00) as ''' + @ColNameSixth + ''',
ISNULL([' + @FifthLastMonth + '], 0.00) as ''' + @ColNameFifth + ''',
ISNULL([' + @FourthLastMonth + '], 0.00) as ''' + @ColNameFourth + ''',
ISNULL([' + @ThirdLastMonth + '], 0.00) as ''' + @ColNameThird + ''',
ISNULL([' + @SecondLastMonth + '], 0.00) as ''' + @ColNameSecond + ''',
ISNULL([' + @LastMonth + '], 0.00) as ''' + @ColNamelast + ''',
ISNULL([' + @CurrentMonth + '], 0.00) as ''Current Last Month Revenue'',
ISNULL([' + @SixthLastMonth + '], 0.00)
+ ISNULL([' + @FifthLastMonth + '], 0.00)
+ ISNULL([' + @FourthLastMonth + '], 0.00)
+ ISNULL([' + @ThirdLastMonth + '], 0.00)
+ ISNULL([' + @SecondLastMonth + '], 0.00)
+ ISNULL([' + @LastMonth + '], 0.00)
+ ISNULL([' + @CurrentMonth + '], 0.00) AS ''Total Revenue''
FROM
( SELECT CustomerNumber, CustomerName, Revenue, [YYMM]
FROM SalesDetails
) SD
PIVOT
( SUM(Revenue) FOR YYMM IN ( [' + @SixthLastMonth + '], [' + @FifthLastMonth + '], [' + @FourthLastMonth + '], [' + @ThirdLastMonth + '], [' + @SecondLastMonth + '], [' + @LastMonth + '], [' + @CurrentMonth + '])) as pvt
ORDER BY
CustomerNumber, CustomerName
'
EXEC(@SQL)
Monday, April 13, 2009
Reporting Services Print from SharePoint: "Unable to load client print control"
We attempted to install the RSClientPrint.cab manually and register the DLLs but that didn't work. Even creating a new MSI file did not work. Finally, after going through various steps, we were able to allow the ActiveX controls to be silently installed within the locked-down environment. In order for the steps below to work, any group policy must "Allow download of signed ActiveX controls". This was confirmed to be resolved using IE 6.0, 7.0, & 8.0.
Here is the summary of our troubleshooting for issue “Unable to load client print control” :
1. When we tried to print any report from Sharepoint Integrated Reporting Services, we got an error “Unable to load client print control”.
2. We checked that Reporting Services is on 9.00.3042 (SP2) version.
3. Upgraded Report Server with Security Update for SQL Server 2005 Service Pack 2 (KB954606) : http://www.microsoft.com/downloads/details.aspx?familyid=4603C722-2468-4ADB-B945-2ED0458B8F47&displaylang=en
4. That went successful and Report Server version changed to 9.00.3073
5. Then we installed the updated version of the SharePointRS.msi on all Web Front Ends in the SharePoint farm. The new version of the add-in can be found here: http://www.microsoft.com/downloads/details.aspx?FamilyID=1e53f882-0c16-4847-b331-132274ae8c84&DisplayLang=en
6. Verified that the Microsoft.ReportingServices.SharePoint.UI.WebParts.dll has been updated to the correct build of 9.00.3294 by looking at the version of this file in the C:\windows\assembly folder.
7. Now users are able to print the report
Friday, February 27, 2009
BI 'Appliances', reference configurations, and very large-scale Data Warehouses
Well, Microsoft has partnered with Dell and HP to pre-configure and pretest DW ‘reference’ configurations to compete with similar offerings from other big players. The thing I liked seeing was the ‘cost per terabyte’ - granted, these would be for very large implementations, but I thought this was an interesting reference to keep in the back of the mind for high-level scoping.
According to a news article on TDWI.org ...
“DW appliance pricing varies, but -- if Microsoft can come in at its $13,000-per-TB figure -- it can plausibly claim to field one of the cheaper DW appliance entries. On the other hand, "cheaper" in the DW appliance segment is something of a moving target. Prices have plummeted to such an extent that even DW powerhouse Teradata Corp. -- which appliance players Netezza Inc. and Dataupia Inc. like to position as a pricey proposition -- now markets a system (the Extreme Data 1550) that it says sells for about $16,500 per TB.” (Netezza sells a unit for $18,000 per TB) (you can read more at http://www.tdwi.org/News/display.aspx?ID=9325)
The other take away from this is that (for anyone still wondering) - yes - Microsoft SQL Server is truly an Enterprise-class BI platform or we wouldn't even be talking about them with the likes of Teradata and Netezza. Furthermore, the features that are planned for the next version of SQL Server as well as Project Madison mean that exciting things are still to come.
For more info, check out -
Project codename "Madison"
http://www.microsoft.com/sqlserver/2008/en/us/madison.aspx
and for a slide-show about Project Madison...
http://sharepoint.microsoft.com/sharepoint/worldwide/us/southeast/Connections%20BUSINESS%20INTELLIGENCE/Connection%20SE%20Jan%202009%20-%20Madison%20Final.pptx
Wednesday, February 11, 2009
Include Column Headers in Query Result Export



With this option selected, I can now copy (or directly save) data from my query results (in Grid) and have the column headers included saving me the effort of typing the column names in for each resultset I need to provide.








