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 是一對多的結果。
更多 BI 文章請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server)
如果覺得這篇文章看了對您有幫助,請幫助推薦,以方便他人在 BIWORK 博客推薦欄中快速看到這些文章。