SSRS 2008 R2 custom code clear dictionary in render lifecycle event - sql-server

My report gets data from SQL based on a date range, uses custom code to calculate totals by Employee and CategoryType and stores the values in a dictionary. To get the grand total by Employee, in a matrix cell, I call a function and pass it the EmployeeID which returns the accumulated total. A chart is populated with data the exact same way.
Everything works as it should when running the report directly in Report Builder 3.0. However, problems arise when exporting the report to Excel. The first time it's exported, the totals are doubled. Each consecutive time, the multiplier is incremented by one. (1st - doubled; 2nd - tripled; 3rd - quadrupled, etc.). To work around this problem, I override the Init event and clear the dictionary variable (see below for code--notice I'm using the keyword Shared which I found out about on another posted question). Problem solved for Excel as now the dictionary is garbage collected and it will not recalculate from what already exists in memory.
But now a different problem exists. When executing the report via the Web, the matrix populates with data and the totals are correct although the chart only populates with data within a week date range from the current date. Very strange! If I increase the date range to longer than a week, I get a blank chart. The link above implies this may be due to the "report on demand" engine of SSRS but offers no solution to the problem in my mind. I need a solution that will work (showing correct totals and populating chart with data) when exporting the report to Excel and when simply viewing on the Web. I will post screenshots and more info if needed.
I've tried overriding the LoadComplete event (thinking this would be a great time to clear the dictionary as all other controls on page are loaded), but not sure this is possible in SSRS custom code and unsure of the proper syntax.
Is there a different render event I should be using instead to make this work? Anyone ever run across something similar and have a reasonable solution?
public Shared Dim dict As New System.Collections.Generic.Dictionary(Of String, Decimal)
'Override built-in OnInit function and clears dictionary; ensures correct totals when exporting to Excel
Protected Overrides Sub OnInit()
dict.Clear()
End Sub

Related

Report Builder 3.0 Add Data Elements to an Array

I'm currently working on building a dashboard for some hospital metrics. The state requires that we report median key times. This dashboard will need to display the group median time for each day and the total column will need to display the entire dataset's median.
I'm working with Report Builder 3.0 and SQL Server 2014.
I've built a stored procedure that can calculate all these values accurately but accomplishing the mean this way makes expanding the dataset a bit of a monster when a new field needs to be pulled in.
All the articles (like as this one) I've read for calculating median within Report Builder point to needing to display (or insert and hide) all the data from the dataset. This method seems really hacky to me and is going to make this dashboard an absolute monster to try to manage as it grows.
What I need to know is, is there any way to pass the group values to a custom code function? The report has to have access to the group values at some point or else it wouldn't be able to perform the built in aggregate functions (Sum, First, Last, etc) on these groups. If I can't pass these values as an array to a custom code function, does anybody know how the group aggregate functions are built?
Thanks in advance to any who might have a direction to point me.
You can pass the values from the procedure to a function within SSRS and then have it just display the resulting median. I'll just give you summary to get you started.
Create a hidden parameter that allows multiple values.
Set its available values to pull from the dataset that is populated by your stored procedure.
Also set its default value so that all values are selected by default.
Go to the Code section of the report properties to write your function.
The function will take the parameter as an argument like this Public Function MyFunc(ByVal p1 as object()) as Integer
In the function you can refer to p1.Length and an item in the array like so: p1(i)
In the report you can call the function in an expression like this: =Code.MyFunc(Parameters!p1.Value)
Hope this points you in the right direction.

SSRS 2008 Report Dynamic Header works on local machine in BIDS but not from Report Server

I have a report that consists of a single column tablix with a single row that is grouped. Inside the row are three sub reports. The Header of the report is supposed to display three pieces of information unique to the data that the row is grouped on so the header is unique for each.
I attempted to populate the data in the header by having hidden controls in the body that I then referenced in the expressions of the controls in the header. This method didn't work. So I switched to a different possible solution I found which was to populate variables using VB and reference them in the header using =Code.variableName in the expression. The function to populate the variables is called from a hidden control in the body which passes the data as parameters.
This method worked. However, it only seems to work on my local machine and one of my coworker's machines in BIDS preview. When the report is published to the report server, it runs and everything seems fine, except the header values don't populate.
One of the variables is an integer and the other two are strings. The integer value is initialized to 0 and in the report from the report server it shows the initialized value of 0, but the strings are still blank. This makes me think that the function isn't being called or there might be a scope issue? Or it could be something completely different that I'm not familiar with. Possibly a configuration issue with the report server?
Here is the report:
If you can see the tiny little control about halfway down the report on the left side. Its just a tiny little box.. The expression for that control is:
=Code.GetHeaderData(Fields!SEPID.Value, Fields!FullName.Value, Fields!Level.Value)
The Function is:
Shared Public Dim SEPIDVar as Integer
Shared Public Dim FullNameVar as String
Shared Public Dim LevelVar as String
Public Shared Function GetStudentHeaderData(
ByVal sepid as Integer,
ByVal fullname as String,
ByVal level as String)
SEPIDVar = sepid
FullNameVar = fullname
LevelVar = level
End Function
The three header controls have the following expressions:
=Code.FullNameVar
=Code.SEPIDVar
=Code.LevelVar
And then here are some screenshots showing the difference in how the report looks when I preview it locally from BIDS vs running it on sql report server:
The working header when viewing preview in BIDS 2008 on local machine:
(values distorted)
And the broken header when viewing the report from the report server:
I've spent multiple days now scourging the internet for a solution to no avail. Any help in troubleshooting why this is happening would be greatly appreciated!
It seems that the Report Server doesn't run your code before the header is created. Obviously, it seems to work in BIDS.
Can you move the headers down to be part of your report so you can just use the fields?
If not, have you tried using ReportItems? See this TechNeT for more info.

UNION multiple datasets in SSRS outside query

I have two data sets with exactly the same fields that I would like to combine into one dataset.
I cannot put a UNION/JOIN and make it return one dataset in the query as all data is encrypted and gets decrypted by an assembly reference loaded in SSRS (so I cannot run the necessary WHERE's unless I use a filter once the data is in SSRS).
I can return the second dataset using Lookup/LookupSet however I am using this data in a bar chart so (somehow) need both sets of data to display on it. If I was using a tablix I would be able to "hack" it by putting a second tablix without headers underneath the main tablix and show it like that. Unfortunately being a graph I cannot do this.
I also tried running it as one dataset, returning all values and then running "filters" based on category groups in the chart however for whatever reason [bug in SSRS?] the filter on these filters the whole result set, not just the one category group.
Is what I am trying to do possible in SSRS? Seems so basic but after a week of trying I have just about given up!
Merging two datasets is not possible but there may be a way to fake it for your chart purposes by doing a SUM of LookUpSet. What ever field is used as your value needs to be looked up using whatever criteria is being used for your axis.
Let's say you're displaying sales by month. Your value field is SUM(Fields!TotalSales.value) and your date field is Fields!month.value. You'd want to add the value from dataset1 and lookup the total from dataset2 - like:
=Fields!TotalSales.value + Code.SumLookup( LookupSet(Fields!month.Value, Fields!month.Value, Fields!TotalSales.value, "Dataset2") )
Unfortunately, SSRS doesn't let you sum a lookupset (so what's the point of it?), so you have to use custom code to do it.
Function SumLookup(ByVal items As Object()) As Decimal
If items Is Nothing Then
Return Nothing
End If
Dim suma As Decimal = New Decimal()
suma = 0
For Each item As Object In items
suma += Convert.ToDecimal(item)
Next
Return suma
End Function
Stolen from: How to combine aggregates within a group with aggregates across groups within SSRS
You're data is obviously different but the concept should be the same.

Should I use Excel or Access

Currently I'm attempting to determine how to easily be able to enter data either in Excel or Access. I'd use the Data form tool in excel however I'm using a scanner that's configured to send an enter command after a successful scan so it would move to the next cell.
Serial Number Employee Time
123 Brian 8/20/13 8:49:21 PM
213 Brian 8/20/13 8:49:21 PM
334 Nick 8/20/13 8:49:21 PM
I'd like to be able to have an accurate time that the serial number was scanned. As it is right now every time a change was made on the sheet the time updates which really defeats the purpose. I'm currently using this formula:
=IF(A2<>"",NOW(),"")
I'd also like to be able to make the employee name able to be changed but also stay the same during the time when that person is scanning the items. I tried just referencing a dropdown list but, when that field changes so does the rest of the employee field.
I'm guessing that this would be best suited for Access because eventually this will be located in different locations and I'd need to be able to correlate the data.Honestly, I'm just too unfamiliar with it to be able to really create something amazing and have it work.
Any help would be GREATLY appreciated.
Because your serial numbers most likely correlate to some articles and you already think of multiple locations and users, I would suggest using Access.
All this leans more to a datebase, which might not be that intuitive to set up for you as excel would be, but in the end it is way more solid and easier to handle, when it comes to catching dublicates, and making queries.
However, it can be done both ways. But not using now() - because this function would update your time, whenever recalculated. That is why you get the same time over and over.
You would have to create and call a function, which inserts the time as fixed text, not as dynamic content.
On Access however, using a now-function as the default value for a field would work, as the database-entry would be the value not the function.
For Excel you would have to ramp up this function and could create/activate a keylistener, when activating a new Worker:
'This code goes into a VBA-Module
'and can be access on a Worksheet calling =myScan("test")
Public Function myScan(strWorker As String)
Table1.Cells(2, 1) = "1"
Table1.Cells(2, 2) = strWorker
Table1.Cells(2, 3) = Now()
End Function
The Serial-Number can be derived from the row. The main Problem would be to stay on the current/next row to paste the data in.
But you could - just for instance - create an Excel Dialog to select a Worker, then activate a Scan mode and scan ... you could even connect to a Web or Access database, not storing the data in excel, which I would highly recommend ;)
Edit
'This code works, when you place it as the VBA-Code of your worksheet
Private Sub Worksheet_Change(ByVal Target As Range)
'react only on changes on column A
If (Target.Column = 1) Then
'react only on changes on column A when the row is greater than 5
If (Target.Row > 5) Then
'use the current time for Bx
Me.Cells(Target.Row, 2) = Now()
'use the user-name from C2 in Cx
Me.Cells(Target.Row, 3) = Me.Cells(2, 3)
End If
End If
End Sub
Use ALT+F11 open the VBA-Editor and choose the table-object you want to use and place the code above into it, than save as a Macro-Excel-Dokument.
Your worksheet should look like this:
It will automatically update, whenever you change something from A6-Ax.
The short answer is; Excel is NOT a database. If you're going to be reporting on data, and it's going to contain a LOT of data, don't even consider Excel. It's a SPREADSHEET, not a DATABASE.

How to reference calculated fields in a report using SQL Server Reporting Service

I'm writing a report using SQL Server2005 Reporting Service. I have a stored procedure which provides a dataset.
The data is split into Groups in the report. Each group footer has a function (expression) which is an average calculated from the fields in the table (lets call this value X). At the end of the data, I want the report to have a footer which includes the SUM of all the X values.
Note that I dont want a sum of all the fields in the column, but I want a sum of all the calculated values in the group footers. This would be really easy to do in Excel
This problem would seem to be straightforward, but I'm battling to find an answer. I would appreciate any help, thanks.
After searching through the MS Forums, and much trial and error, my findings are that this is a limitation in this version of BIDS. The following are workarounds:
1) Upgrade to a later version of BIDS. I didn't investigate whether my problem would be addressed in a later version because upgrading is not an option at this time.
2) Add a new Calculated Field to the dataset. This didn't work because, for my application, the Calculated Field requires an Aggregate function, which is not supported.
3) The best solution was to add a VB code function to the Report Properties as follows:
Dim Shared totalBalance
Dim Shared Cnt AS Integer
Public Function AddTotal(ByVal balance )
totalBalance = totalBalance + balance
Cnt=Cnt+1
return balance
End Function
Public Function GetTotal()
return totalBalance
End Function
And then reference the code from the group footer as: =Code.AddTotal(Avg(Fields!Amount.Value))
And from the Report Footer as:
=Code.GetTotal()
This solution worked well in the Group Footer, but in the Report Footer at the end of the report, the variables Cnt and totalBalance were reset to 0.

Resources