开篇介绍
SSRS 报表中有一些高级的技巧,平常很少用到。下面我通过这个案例来展现一下如何在实际开发中使用它们,并且如何解决一些实际的需求。
这张报表分别统计了不同的 Product 产品在不同的月份的 Order 订单数量,Due 付款数量和 Ship 装船数量。
Start Date 和 End Date 的时间范围作为筛选,很显然第一个 Matrix 是以 Order Date 作为比较条件,第二个是以 Due Date, 第三是以 Ship Date 作为比较条件。
现在需要变成这样的一种需求 - 在一个 Matrix 中来展现按月分组之后的 Order Count, Due Count, Ship Count。
复杂性
第一 ,无法确定到底是按照 Order Date, Due Date 还是 Ship Date 作为列的父分组,如果按照 Order Date 分组,那么 Due Count 和 Ship Count 下的数据又该如何统计。很显然订单日期在 2007年7月份的这笔订单,它的 Due Date 有可能落在 2007年8月份,它的 Ship Date 有可能落在 2007年9月。但是在一个 Dataset 中只能要么按照 Order Date,要么按照 Due Date 或者 Ship Date 来过滤。因此既不能按照 Order Date,也不能按照 Due Date 或者 Ship Date 来作为查询的 WHERE 条件。
第二,在月份的分组之下没有条件能够判断那些数据是属于 Order Count, Due Count 和 Ship Count。月份之下的分组没有依据,无法分组。
当然解决以上两个问题在 SQL 查询中是比较容易解决的,就是通过硬编码将三个查询出来的 Dataset 合并成一个更大的 Dataset 并设置列标识。
但是现在的问题是,这三个 Dataset 已经都存在了,并且我们假设是没有办法改变的情况下如何解决这个问题,比如数据源使用的是无法通过 SQL 查询的 Report Model 的 Entity。
解决方案
三个 Dataset 的示例查询语句,可以自行替换日期参数。
USE AdventureWorksDW2008R2
GO
SELECT DP.EnglishProductName,
F.SalesOrderNumber,
DD.FullDateAlternateKey AS 'OrderDate'FROM dbo.FactInternetSales AS F
INNER JOIN dbo.DimProduct AS DP
ON F.ProductKey = DP.ProductKey
INNER JOIN dbo.DimDate AS DD
ON F.OrderDateKey = DD.DateKey
WHERE DD.FullDateAlternateKey BETWEEN '2007-01-01' AND '2007-09-30'AND DP.EnglishProductName IN ('Adjustable Race',
'All-Purpose Bike Stand',
'AWC Logo Cap',
'BB Ball Bearing',
'Bearing Ball')
SELECT DP.EnglishProductName,
F.SalesOrderNumber,
DD.FullDateAlternateKey AS 'DueDate'FROM dbo.FactInternetSales AS F
INNER JOIN dbo.DimProduct AS DP
ON F.ProductKey = DP.ProductKey
INNER JOIN dbo.DimDate AS DD
ON F.DueDateKey = DD.DateKey
WHERE DD.FullDateAlternateKey BETWEEN '2007-01-01' AND '2007-09-30'AND DP.EnglishProductName IN ('Adjustable Race',
'All-Purpose Bike Stand',
'AWC Logo Cap',
'BB Ball Bearing',
'Bearing Ball')
SELECT DP.EnglishProductName,
F.SalesOrderNumber,
DD.FullDateAlternateKey AS 'ShipDate'FROM dbo.FactInternetSales AS F
INNER JOIN dbo.DimProduct AS DP
ON F.ProductKey = DP.ProductKey
INNER JOIN dbo.DimDate AS DD
ON F.ShipDateKey = DD.DateKey
WHERE DD.FullDateAlternateKey BETWEEN '2007-01-01' AND '2007-09-30'AND DP.EnglishProductName IN ('Adjustable Race',
'All-Purpose Bike Stand',
'AWC Logo Cap',
'BB Ball Bearing',
'Bearing Ball')
新建一个报表,并拖放一个 Matrix,并预先只选择包含有订单的 Dataset,行以 English Product Name 来分组,列暂时不动。
在 Column Group 右键选择添加一个分组,暂时按照 OrderDate 分组,不要选择 ‘Add Group header’。
这样一来 ColumnGroup 之上就有一个 OrderDate 的分组了,继续选择 ColumnGroup 并选择添加 Adjacent After。
Adjacent Group - 相邻的分组,这样在 OrderDate 父分类下就可以创建一个或者多个并列的子分组了。
ColumnGroup 是以 Order Date 作为分组的依据的,这里为它添加两个同样以 Order Date 分组的相邻分组。
要注意这个分组的括号的变化,与直接在子分组下邮件添加新列是有区别的。
将所有按照 OrderDate 分组的条件格式化,因为 Order Date 本身是类似于年-月-日结构的,但是我们分组的条件是基于年-月级别。
只有按照这种分组方式,才可以将第二个相邻分组和第三个相邻分组的列名更改掉。
因为上面分组的条件是按照 yyyy-MM 格式化的,为了后面条件对比的统一性,为每一个 Dataset 添加一个 KEY,也按照这个格式格式化。
添加了 OrderMonthKey,DueMonthKey 和 ShipMonthKey。
在第二个相邻和第三个相邻分组的聚合数据处要实现这样的逻辑:
由于父分组都是按照 Order Date 来分组的,并且所有三个子分组也都是按照 Order Date 来分组的,所以如果按照第一个子分组下的 Count(SalesOrderNumber) 复制到第二个或者第三个子分组的话,结果应该是一样的。
这样就先解决了第一个分组的问题。
第二就是继续以 OrderDate 作为查找条件,去查找有哪些 Due Date 和 Ship Date 在同一个月的 SalesOrderLineNumber。
因此在第二个子分组中,计算在这个月中的 Due Count 就是如下逻辑 -
通过当前 Order Month,到 DS_DUE Dataset 中去比较有哪些 Due Month 和 Order Month 是一样的,于是把查询到的 SalesOrderNumber 全部返回回来。最后通过 .Length 来获取返回的 SalesOrderNumber 数量。
这个逻辑也用到 Ship 分组统计上。
再来比较一下之前的报表
和更改之后的报表 - 修改之后的报表出现了问题。所有产品的 Due Count 和 Ship Count 都是一样的,结果不正确。
错误的原因是什么 ? 因为在 Lookup 查找的时候没有考虑行分组的问题,也就是说 Lookup 的时候只考虑了比较月份,而没有考虑要同时比较产品和月份,即不同的产品下,不同月份下的 Sales Order Count。
引申了一个新的技巧 - 如何在 LookupSet 的时候比较多个列 ? 其实很简单,拼接字符串即可。
保存并查看报表,这次就都正确了。
上面的这些报表处理技巧在实际开发过程中非常实用,可以在不更改原有 Dataset 和查询结构的情况下很容易的实现了新的需求。
关于 LookupSet 的使用可以参考 MSDN LookupSet,同时 Lookup 函数也是非常常用的,不同的是 Lookup 是返回一对一的结果,而 LookupSet 是一对多的结果。