Tag: Reporting

Darstellung herausgefilterter Zeit-Kategorien

Arno Cebulla

Ein typisches Verhalten in Reporting-Services ist es, dass Daten, die mit Parametern herausgefiltert werden, auch im Bericht nicht dargestellt werden. Beispiel: In diesem Chart werden die Umsätze für ein Jahr dargestellt: 

 Wenn nun nur noch die Monate Januar-April im Month-Parameter ausgewählt werden, erscheint folgendes Chart:

 

Die Daten werden per MDX aus einem Cube gelesen und im Dataset mit einem Subselect oder in der WHERE-Bedingung gefiltert:

SELECT

 {[Measures].[Sales Amount]} ON 0,

 NON EMPTY

 {([Date].[Calendar].[Date].ALLMEMBERS )} ON 1

 FROM

 (SELECT ( STRTOSET(@DateCalendarYear, CONSTRAINED) ) ON 0

 FROM

 (SELECT ( STRTOSET(@DateMonthofYear, CONSTRAINED) ) ON 0

 FROM [Adventure Works]))

Der Kunde wünscht nun, dass die übrigen Monate aber dennoch (mit dem Wert 0) angezeigt werden sollen. Hierfür gibt es zum einen die Möglichkeit, dieses bereits im MDX-Query zu lösen. Dies geschieht über einen String-Vergleich in einem IIF-Block. Leider ist diese Methode recht unperformant.

Ein besseres Ergebnis kann man mit dieser Methode erzielen, vorausgesetzt man verwendet SQL Server ab Version 2008 R2:

Im Dataset werden zunächst alle Daten für das Jahr geholt:

SELECT

 {[Measures].[Sales Amount]} ON 0,

 NON EMPTY

 {([Date].[Calendar].[Date].ALLMEMBERS )} ON 1

 FROM

 (SELECT ( STRTOSET(@DateCalendarYear, CONSTRAINED) ) ON 0

 FROM [Adventure Works])

Dann wird ein zweites Dataset „GueltigeMonate“ erstellt, in dem die Monate anhand des Parameters gefiltert werden:

WITH

MEMBER [Measures].[ParameterCaption] AS [Date].[Month of Year].CURRENTMEMBER.MEMBER_CAPTION

MEMBER [Measures].[ParameterValue] AS [Date].[Month of Year].CURRENTMEMBER.UNIQUENAME

MEMBER [Measures].[ParameterLevel] AS [Date].[Month of Year].CURRENTMEMBER.LEVEL.ORDINAL

SELECT

{[Measures].[ParameterCaption], [Measures].[ParameterValue], [Measures].[ParameterLevel]} ON COLUMNS ,

[Date].[Month of Year].ALLMEMBERS ON ROWS

FROM

(SELECT STRTOSET(@Monate,CONSTRAINED) ON 0

FROM [Adventure Works])

Nun müssen beide Datasets miteinander verbunden werden. Hierzu werden die Eigenschaften der Serie geöffnet:

Im Feld für die Daten (Value) wird nun der Lookup gegen das Dataset „GueltigeMonate“  hergestellt. Hierfür wird die Funktion „Lookup“ benötigt. Die Syntax sieht so aus:

=IIF(LEFT(Fields!Month.Value,LEN(Fields!Month.Value)-5)=

LOOKUP(LEFT(Fields!Month.Value,LEN(Fields!Month.Value)-5), Fields!ParameterCaption.Value,Fields!ParameterCaption.Value,”GueltigeMonate”),

Sum(Fields!Sales_Amount.Value),0)

Diese Syntax bewirkt folgendes:

Wenn der Monatsname im Haupt-Dataset gleich einem Monatsnamen ist, der im gefilterten Monats-Dataset vorhanden ist, so wird der Wert angezeigt, ansonsten wird die Zahl 0 angezeigt.

Wenn nun im Bericht die gleiche Selektion wie am Anfang beschreiben durchgeführt wird, ergibt sich das gewünschte Bild: 

Die Anzeige-Geschwindigkeit konnte mit der Lookup-Methode gegenüber der Behandlung im Query halbiert werden. In einem der nächsten Blog-einträge werde ich die Lookup-Funktion dann nochmal näher beleuchten.

  • Share/Bookmark

Reporting Service Warmup

Daniel Snellen

Der erste Aufruf eines Reports vom Reporting Service dauert immer recht lange.
Um dieses Verhalten dem Benutzer gegenüber auszublenden, kann man einen sogenannte Warmup durchführen.
Es gibt verschiedenste Wege ein solches Warmup durchzuführen, ich werde hier einen Weg mittels des Tools WGET beschreiben. 
GNU WGET ist ein kostenloses Komandozeilenprogramm zum Herunterladen von Dateien aus dem Internet. >Download<

Wir nutzen Wget zum beleben des Reporting Service, in dem wir die Startseite das Reportmanagers aufrufen, besser gesagt wir laden die Startseite mittels WEGT herunter.
Folgender Befehl muss ausgeführt werden.

wget  –http-user=BEUTZERNAME –http-password=PASSWORT http://SERVERNAME/Reports/ –delete-after


http://SERVERNAME/Reports“
Dieser Abschnitt gibt die URL das Report Managers an und muss jeweils noch angepasst werden.

“ –http-user=BENUTZERNAME“
Dieser Abschnitt gibt den Benutzer an mit dem sich WEGT am Report Manager anmeldet.
Bei Domain Benutzer muss DOMAIN\BENUTZERNAME angeben werden.

“ –http-password=PASSWORT“
Dieser Abschnitt gibt das Passwort des Benutzers  an.
Da der Benutzername und das Passwort hier im Klartexte gespeichert werden, sollte hier ein Benutzerverwendet werden, der nur die Startseite des Reporting Service aufrufen kann und sonst keine Reports sieht.
Des Weiteren sollte man darauf achten das Passwort des Benutzers nicht abläuft, weil dann wget keinen Zugriff auf den Reportmanager mehr hat und das Script angepasst werden muss.

“ –delete-after“
Dieser Abschnitt gibt an das die heruntergeladenen Dateien abschließend wieder gelöscht werden, damit das lokale Verzeichnis nicht überläuft.

Wenn der Reporting Service über HTTPS erreichbar ist und es sich um ein privates SSL Zertifikat handelt, kann es sein das Wget das Zertifikat nicht überprüfen kann und abbricht.
In diesem Fall muss dann noch der Parameter “ –no-check-certificate” hinzugefügt werden.

Der gesamte Wget Befehl sollte in einer Batchdatei gespeichert und einem geplanten Task eingebunden werden.
Dieser Task sollte dann so geplant werden, dass er z.B. jeden Morgen um 7Uhr läuft.

Achtung vor den Befehlen kommen immer 2 Minuszeichen!! Leider wird das nicht immer korrekt dargestellt.

Recycle Time

In der rsreportserver.config wird die Recycle Time definiert. Diese gibt an nach welcher Laufzeit der Webdienst des RS zurückgesetzt wird.
Standard ist hier 720, die Angabe ist in Minuten.

<RecycleTime>720</RecycleTime>

Dieses Zeitangabe sollten man bei der Planung des Task beachten und gegebenen Falls anpassen.

  • Share/Bookmark

Implementing drill-down in a SQL Server 2008R2 Reporting Services Map (Step by Step)

Hilmar Buchta

SQL Server 2008R2 SSRS

In this post I will show how to implement drill down into a more detailed view for a SQL Server 2K8 R2 Reporting Services (SSRS) map.

STEP 1. Acquiring map data and importing the data into a SQL Server database

For this example, I’m going to use a map of Germany. The map can be downloaded from http://www.diva-gis.org/gData

image

From my download I got a ZIP-file containing the ESRI shape files needed for import. For our needs it is important that we get different levels of granularity:

   
DEU_adm0.* Borders of Germany
DEU_adm1.* Map at German state level
DEU_adm2.* administrative regions
DEU_adm3.* most detailed

 

image image image image
DEU_adm0 DEU_adm1 DEU_adm2 DEU_adm3

 

STEP 2. Loading the map into a SQL Server database

I’m using Shape2sql here. The tool can be downloaded from http://www.sharpgis.net/page/sql-server-2008-spatial-tools.aspx. In order to actually load the data files into a database, we first have to create this database. In my case, it’s located on my localhost machine with the name GIS (just an empty database).

The following screenshot shows the loading of the DEU_adm3.shp file. In my case I had to deactivate “Create Spatial Index” as I got an error with this one.

image

After loading DEU_adm1.shp, DEU_adm2.shp and DEU_adm3.shp you will find three new database tables in your database:

image

We can now use SQL Server Management Studio or SQL Spatial Query Visualizer (contained in the download for Shape2sql above) to visualize our maps. I used the latter one to create the map screenshots shown above.

STEP 3. Creating the report

Now we can actually start building our report. I created a new Report Server project using Business Intelligence Development Studio. In this report project I created a shared data source to connect to my GIS database.

Then we can finally start with the report. I created a new report and added the shared data source to my report. Here are the steps:

image

image

Next step is to add a dataset for our map data. Here are the steps:

image

image

For the query, copy and paste the following SQL query into the Query field:

with mapdata as (
select 3 as MapLevel, ID_1,NAME_1,ID_2,NAME_2,ID_3,NAME_3,geom, NAME_3 As Lbl from dbo.DEU_adm3
union all
select 2 as MapLevel, ID_1,NAME_1,ID_2,NAME_2,null,null,geom, NAME_2 from dbo.DEU_adm2
union all
select 1 as MapLevel, ID_1,NAME_1,null,null,null,null,geom, NAME_1 from dbo.DEU_adm1
)
select * from mapdata
where
    (ID_1=@LocID1 OR @LocID1 Is NULL)
and (ID_2=@LocID2 OR @LocID2 Is NULL)
and (ID_3=@LocID3 OR @LocID3 Is NULL)
and MapLevel=@MapLevel

Note, that this query actually defines a union of all different layers of detail into one result set. If you like you can also look at the query results in Management Studio (of course you have to remove the where condition and I would recommend to order by id_1, id_2, id_3):

image

Here you can see, how the detail levels (field MapLevel) are adding more information.

Let’s go back to our report. After adding the query you will notice that the parameters of the query are automatically added to the report as shown below:

image

For our report, we hide all parameters by setting the visibility option to hidden. Also, we allow NULL-values for each parameter. Here are the details:

image

For the MapLevel parameter (and only for this one), we’re going to define an initial detail level of 1:

image

Now we’re ready to add the map to the report. From the toolbox simply drag’n’drop a map on your report:

image

After adding the map we have to decide for a data source. Of course we’re using the SQL Server here:

image

Next, we need to choose from our datasets. Since we only have defined one, it’s an easy choice:

image

I got an error message here saying that the data could not be loaded. We ignore this error here. In the next screen leave the defaults and just press “Next”.

Now for the map type, choose the basic map and continue:

image

Finally you can decide for a style (which I leave up to you) and you’re done. You should now see a map (not with our data) on your report. You can still make some changes with the map (move it around, add a title, remove the legend).

When using “Preview” you should already be able to see a map like this:

image

STEP 4. Adding Drill-Down

If you click into the map, you should be able to see an additional layer menu to the right of the map. From this menu open the context menu for the first and only layer and click “Polygon Properties…”:

image

On the General Tab we define the [Lbl] field of our query as the label for our map data:

image

On the action tab, we define the actual drill-down. In this case, we’re going to link our report with itself (just choose “Go To Report” and select the name of the current report from the drop-down list). Next we have to map all the parameters. Click the “Add” button four times and add the following mappings:

LocID1 –> [First(ID_1)]
LocID2 –> [First(ID_2)]
LocID3 –> [First(ID_3)]
MapLevel –> =iif(Parameters!MapLevel.Value>=3,3,Parameters!MapLevel.Value+1)

image

By doing so we’re going to link our report parameters to the selected area on the map (id columns of the different elements). Also, we increment the MapLevel with each click to get to the next detailed level as shown in the expression for the MapLevel (use the expression editor to compose the above expression or simply copy it into the editor).

That’s all we need to do in order to create the report. We can now test it in the Visual Studio environment or deploy it to a report server. This is how the report looks in the report server if you first click on “Nordrhein-Westfalen” and then on “Düsseldorf”. Simply use the browser’s back-button to go back one step.

image

Of course you can also use some data aware shading of your map, but for the current post, I’ll leave it as it is.

  • Share/Bookmark

How to summarize data dynamically in Buckets, without changing the dataset

Arno Cebulla

   

SSRS 2008; Data basis: AdventureWorks 2008  

The results are to be represented in chart as bars. The data are to be summarized dynamically in groups. The dataset is used in other tables and diagrams and should not be changed.  

Solution: In the characteristics of the Category Group Group Expressions can be inserted. The Expression accesses report parameters, which can be filled by other datasets. Example:  

=Fields!Reseller_Order_Count.Value<= Parameters!StartBucket1.Value  

In this example all data are summarized, whose value is smaller or equal to the parameter. Also the Expression of the announcement is accordingly dynamically filled:  

=IIF(Fields!Reseller_Order_Count.Value <= Parameters!StartBucket1.Value,”1-” & Parameters!StartBucket1.Value,  

IIF(Fields!Reseller_Order_Count.Value>Parameters!StartBucket1.Value AND Fields!Reseller_Order_Count.Value<=Parameters!StartBucket2.Value, 

Parameters!StartBucket1.Value+1 & “-” & Parameters!StartBucket2.Value,  

 IIF(Fields!Reseller_Order_Count.Value>Parameters!StartBucket2.Value AND Fields!Reseller_Order_Count.Value<=Parameters!

StartBucket3.Value,Parameters!StartBucket2.Value+1 & “-” & Parameters!StartBucket3.Value,”>” & Parameters!StartBucket3.Value)))  

For each group a parameter is needed. As many as desired groups can be provided. If the Expression should not supply value, the group is not indicated, charts behaves also here dynamically. There should be values, which are larger than the highest value of a parameter, these are indicated automatically in their own group. In the following example data with 3 groups/parameters are indicated, the fourth group contain the values, which are larger than the indicated highest parameter 10:

 

image  

 

  • Share/Bookmark

Pie chart with ‘others’ category (collected data)

Hilmar Buchta

SQL Server 2005 | SQL Server 2008

Pie chart with too many categories don’t make much sense. The following screenshot shows the order count from the AdventureWorks OLAP database by subcategory (no selection on date here for this example) as a pie chart:

image

Fortunately, SQL Server 2008 Reporting Services adds a feature to collect all slices below a certain threshold (either as a fixed value or as a percentage) as shown below:

image

To get this result you have to check the custom attributes (properties) of the chart series (either by selecting the pie itself or by choosing the chart series in the property box picker)

image

In our example I collected all slices with a value of less than 3% to one single slice with the name ‘Other’. You can even show the other values as an exploded pie chart (although I think it’s more confusing).

In cases where you want to show just a certain number of slices (instead of using a threshold) or if you are using Reporting Services 2005 which doesn’t support the collected slice, you may do the collection by MDX:

WITH
  SET SelectedSubCategories AS
    TopCount
    (
      Order
      (
        [Product].[Subcategory].[Subcategory]
       ,[Measures].[Order Count]
       ,DESC
      )
     ,10
    )
  SET OtherSubCategories AS
    [Product].[Subcategory].[Subcategory] – SelectedSubCategories
  MEMBER [Product].[Subcategory].[Other] AS
    Aggregate(OtherSubCategories)
SELECT
  [Measures].[Order Count] ON 0
,NON EMPTY
    {
      SelectedSubCategories
     ,[Product].[Subcategory].[Other]
    } ON 1
FROM [Adventure Works]
CELL PROPERTIES VALUE;

The idea is quite simple. First, you create a set with the number of slices you want to see (I called it SelectedSubCategories here). Then you can simply get all other categories using a set minus operation (I called it OtherSubCategories here). Finally you create the ‘Other’ member in the dimension as an aggregate of the last set.

image

Of course, you can even make the parameter for the number of slices a report parameter so the user can choose how many slices are shown in the diagram.

  • Share/Bookmark

How to round detail rows and totals?

Hilmar Buchta

SQL Server 2005 | SQL Server 2008

There had been quite some posts about rounding values in MDX or on a report. For most cases we want to do the rounding in the presentation layer of the application (like with other formatting issues too) using formatting or maybe built-in round functions.

One issue with rounding is that the sum over the rounded details does not necessarily match the rounded total as in the following simple example:

Region Exact value Rounded for display
A 0.25 0
B 0.25 0
C 0.25 0
D 0.25 0
Total 1.00 1

As you can see from the last column, the sum of the rounded details is zero (as every rounded detail is zero), but the rounded total is 1 (as the total is exactly 1).

Basically you have three options for this rounding problem:

1. Round details and totals individually (like in the last column of the table above)
For our example this means that we have 0+0+0+0 giving a total of 1.

2. Compute the total as the sum over the rounded details (round first, then do the sum)
For our example this would mean 0+0+0+0 giving a total of 0

3. Adjust the detail rows so that they match the total when the total is computed by rounding the exact total
For our example this would mean something like 0+0+0+1 giving a total of 1

Option 1 is my preferred solution for most cases. Rounding involves some kind of error but option 1 doesn’t have other side effects like with options 2 and 3. Anyway, especially in financial reporting, option 1 would not be accepted as the detail rows doesn’t sum up to the total. This leaves some kind of uncertainty. Is there an error in the computation? Are we missing some values? Usually you can handle this be placing a remark on the report like "Totals may not match details because of rounding".

Option 2 sounds very nice at first but has some serious drawbacks. Usually the total is more meaningful than the details, so you might want to compute the total e.g. not by region but also by product group. Imagine the details for the product group are 0.5+0.5. Both would round to 1 so the total would be 2. Now for someone comparing the total by region (0 in this case) and the total by product group (2 in this case) the result is very confusing. Furthermore the error can get really serious when there are many records. So whatever your requirement is, I don’t recommend this option.

Option 3 is more complicated than option 1 and 2. It needs to adjust one or more elements which may also be confusing. In the example above we had 0+0+0+1=1, so the total really matches the details but it seems like only the four detail row is more significant. This could lead to wrong decisions. Another disadvantage of this option is that the detail usually is a total of some kind too (an aggregated value). So you might compare the value for region D with some other report showing the same value. This could also cause confusion when those two values doesn’t match.

So option 1 and option 3 are still under consideration. While option 1 can be easily implemented in the commonly used frontends, option 3 is more difficult. The remaining part of this post is about option 3.

As option 3 modifies the values in some way, I don’t want to implement this in the cube but solve the problem in the presentation layer of the application. There would be one reason however why you may think about implementing this kind of rounding in mdx: The presentation layer is not capable of doing so.

So maybe this post is a little bit off topic, but in the following I’m showing how to do this with Reporting Services (SSRS) instead of doing it by mdx.

As an example, I’m using a report with a simple mdx query based on the Adventure Works cube:

select {[Measures].[Amount] } on 0,
[Date].[Month of Year].[Month of Year] on 1
from [Adventure Works]
where ([Account].[Gross Sales],[Date].[Calendar Year].&[2003])

In order to get some rounding issues let’s say we want the Gross Sales as millions. I added a computation to the report’s dataset like this:

image

Now, before getting into detail for the computation, let’s look at the result (rounded values are rounded to one digit):

 image

The third columns is what we saw as option 2 earlier in this post. We rounded the values and added the rounded values up to the total. This results in 25.3 and as you can see this doesn’t match the rounded total as 25.574665 would round to 25.6, not to 25.3.

The last column now shows the modified detail rows. As you can see, some of the detail values are slightly different resulting in the correct rounded total.

But how could this be done? In order to make things a little bit more easy to understand I added three more columns to the reporting showing the approach I took:

image

The "Rounding error" column simply computes the error we made for each line. Therefor the expression looks like this:

=Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1)

The "Running rounded error" column computes the rounded running total over the previous column using this expression:

=Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
,1)

Actually this is the cumulated error that we have to add to the original rounding. But we are just interested in the steps of the this cumulated error. For example, the first values are 0, 0.1, 0.1. For the second line our cumulated error goes from 0 to 0.1, so we will want to add 0.1 to the rounded value. The difference of the second and the third line (both 0.1) is 0, so we don’t need to make any corrections.

Therefor we also take the cumulated rounding error of the previous line (simply but subtracting the current line’s value) using this expression:

=Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
-(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1))
,1)

Finally we can compute the difference in the last column by combining the last two expressions into one:

=Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
,1)
-Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
-(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1))
,1)

This difference gives the correction for our rounded value column, so finally here is the "Corrected detail rows" column:

=Round(Fields!AmountMio.Value,1)+
Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
,1)
-Round(
RunningValue(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1),Sum,Nothing)
-(Fields!AmountMio.Value-Round(Fields!AmountMio.Value,1))
,1)

Again, it’s rather complicated to handle this kind of rounding and the way I showed here might not be perfect. Maybe you want to adjust the detail rows with the highest error instead of cumulating the error from line to line until the error itself round up and can be used to correct the next detail row.

But it might still be helpful when your requirement is to present comprehensible and traceable totals on your report and you’re tempted to write complicated mdx code as the report’s data source.

  • Share/Bookmark

  • Kategorien

  • Copyright © 1996-2011 ORAYLIS Blog. All rights reserved.