基於數據倉庫上的 SSRS 報表展示,一般可以直接通過 SQL 查詢,存儲過程,視圖或者表等多種方式將數據加載並呈現在報表中。但是如果是基於 Cube 多維數據集的數據查詢,就不能再使用 SQL 的語法了而應該使用 MDX 查詢。關於 MDX 和其它 SSRS 的文章,請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server)
這是我們要實現的報表效果,使用的數據庫示例是 MDX Step by Step 2008 的 SSAS DEMO 數據庫。
收起的效果 - 按 Product Category 分組聚合並呈現所有財年的銷售情況。
展開之后能夠看到 Product Sub Category 的銷售數據。
通俗一點的講,從數據倉庫到 Cube 的過程就是將平面數據立體化的過程,在這個過程中加入了從各個不同角度對數據的聚合。而從 Cube 到 SSRS 報表的過程又恰恰相反將立體化的數據平面化的結果。
分析上面的需求,其中需要娶到 Category, Subcategory, Calendar Year 以及 Reseller Sales Amount 的數據。SSRS 報表可以對平面化的數據非常快速的分組和聚合的,因此上面的需求我們整理一下就得到了這樣的一個數據表原型。
假設我們基於上面的這張表去創建 Cube 的話,很顯然 Cube Dimension 應該包含了 Product 和 Date 這兩個維度和一個度量值維度- Reseller Sales Amount , 每一個維度的屬性層次結構可以理解為在 Cube 空間中的一個軸。因此,Product 這個維度至少可以分出 Product Category 和 Product Sub Category 這兩個屬性層次結構,Date 維度我們假設這里只有 Calendar Year 這一個屬性層次結構,那么再加上度量值維度就構成了 Cube 空間的四條軸,四條軸交匯定位到空間的一個點,這個點是一個單元格,它包含了 Reseller Sales Amount 的值。
這是從 Cube 空間體的角度來闡述從 DW 到 Cube 的變化,但是 SSRS 中表格數據中只支持二維數據組合,因此要把 Cube 空間的立體數據給拉平了再呈現回來。
創建報表並新建數據源,這時的數據源連接方式與之前連接到 SQL Server 數據庫不同,連接的是 Analysis Services 。
新建 Dataset,並且指定剛才新建的數據源。因為數據源的改變,SSRS Dataset 中的 Query Designer 將對應的調整為支持 MDX 查詢的界面。
Query Designer 中可以看到指定的分析服務的數據庫以及各維度,層次結構和度量值組。
也支持編寫 MDX 查詢代碼,特別是比較復雜的業務邏輯的情況下,就需要自己手工編寫 MDX 查詢。
我們直接使用拖拽的方式快速開發一個 MDX 查詢報表,並且現在要做的就是讓這幾條軸交匯一下來展現我們扁平化的數據表原型。記住:每一個屬性層次結構就是一條軸,包括度量值維度。這幾條空間里的軸在數據表上最直接的反映就是形成列,每一條數據的形成就是四條軸在 Cube 空間交互的結果。
保存之后,看到 MDX 查詢已經自動編寫好了。
整理一下格式,看看這個 MDX 查詢是怎么寫的。
SELECT NON EMPTY { [Measures].[Reseller Sales Amount] } ON COLUMNS, NON EMPTY { ( [Product].[Category].[Category].ALLMEMBERS *
[Product].[Subcategory].[Subcategory].ALLMEMBERS *
[Date].[Calendar Year].[Calendar Year].ALLMEMBERS ) } DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS FROM [Chapter 3 Cube] CELL PROPERTIES VALUE, BACK_COLOR, FORE_COLOR, FORMATTED_VALUE, FORMAT_STRING, FONT_NAME, FONT_SIZE, FONT_FLAGS
去掉一些不關鍵的屬性,簡化一下就是 -
SELECT NON EMPTY { [Measures].[Reseller Sales Amount] } ON COLUMNS, NON EMPTY { ( [Product].[Category].[Category].ALLMEMBERS *
[Product].[Subcategory].[Subcategory].ALLMEMBERS *
[Date].[Calendar Year].[Calendar Year].ALLMEMBERS ) }ON ROWS FROM [Chapter 3 Cube]
與前面在 Query Designer 上的查詢結果相比一下這個 MDX 查詢在 SQL Server Analysis Service 上面的查詢結果,Query Desinger 的查詢結果扁平化了,更容易理解成一個普通的數據表。而在這里,列頭的構成比較復雜,它本想描述一下這種立體的感覺,無奈在二維平面的世界里它也描述不出來。
注意,這里的 MDX 查詢在 Columns 只能是 Measure 度量值。
創建好了 Dataset 之后就可以直接使用這個平面數據了,拖放一個 Matrix 過來。Matrix 和 Tabular 最大的區別就是 Matrix 可以直接形成行和列的聚合。
對比一下拖放之間和拖放之后的 Matrix 格局便於大家理解。Row 上放的應該是 Category 和 Subcategory,列上放的應該是 Calendar Year,被聚合的數據應該是 Reseller Sales Amount。但是這里,我們先放 Subcategory,然后在 Subcategory 基礎上添加 Group - Category。
選中 Subcategory 然后右鍵添加一個 Group - Parent Group 。
Product Subcategory 可以按照 Category 分組,添加一個 Group Header。
添加完了之后的效果,不過接着就應該刪除 Category 這一列,讓 Category 這個元素顯示在 Subcategory 元素之上,並且添加 Category 級別對 Reseller Sales Amount 的 SUM 聚合。
對比上下這種變化,基本上就完成了報表數據的分組聚合設計。
接着調整一下,然后上色。
為 Category 添加一個 Total。
基於 Calendary Year 也添加一個 Total。
添加完了后上上色,調整一下格式。
將標題 Subcategory 改成 Category ,並且設置 Subcategory 按照 Category 的點擊來展現收縮和展開效果。
自此就已經完成了一個簡單的 MDX 查詢在 SSRS 報表上的展現。不過,如果要添加參數比如希望能夠先篩選 Category 和 Subcategory,回到 Dataset 里的 Query Designer 添加 Parameter 。
保存並刷新,SSRS 為自動創建兩個參數。
預覽報表並選擇參數。
最后查詢出來的結果。
分析一下在 Query Builder 中的 MDX 查詢語句,我就以 MDX Step By Step 上面的例子來說吧,這個 MDX 查詢在 SSAS 查詢分析器中沒有問題。
但是同樣的語句放到 Query Designer 中就有問題,提示這種錯誤。
The query cannot be prepared: The query must have at least one axis. The first axis of the query should not have multiple hierarchies, nor should it reference any dimension other than the Measures dimension.. Parameter name: mdx (MDXQueryGenerator)
其中要注意的有三點:
- MDX Query 必須至少要有一個軸。
- 第一個軸中的查詢不能包含多個層次結構,只能有一個。
- 除了 度量值維度 Measure Dimension 之外不能引用其它任何的維度。
再來看看我們的帶參數的 MDX 查詢語句。
SELECT -- COLUMNS 軸也就是第一個軸上的維度是度量值維度
NON EMPTY { [Measures].[Reseller Sales Amount] } ON COLUMNS, -- ROWS 上由多個屬性層次結構的成員構成 SET 集
NON EMPTY { ( [Product].[Category].[Category].ALLMEMBERS *
[Product].[Subcategory].[Subcategory].ALLMEMBERS *
[Date].[Calendar Year].[Calendar Year].ALLMEMBERS ) }ON ROWS FROM
-- 第三層查詢基於前兩層查詢篩選之后的結果
( -- 第二層查詢,根據參數 Product Subcategory 並根據第一層查詢的結果返回相應的 Subcategory
SELECT ( STRTOSET(@ProductSubcategory, CONSTRAINED) ) ON COLUMNS FROM ( -- 第一層查詢,根據參數 Product Category 決定了這個層次結構上所有的 Category 成員
SELECT ( STRTOSET(@ProductCategory, CONSTRAINED) ) ON COLUMNS FROM [Chapter 3 Cube] ) )
其中最重要的就是 STRTOSET 函數的使用,在 SSRS 加載的時候會首先列出所有 Product Category 成員供我們選擇。
就相當於 -
選擇完了 Bikes 之后,Subcategory 刷新完然后選擇 Mountain Bikes 和 Road Bikes
就相當於
整個 MDX 查詢翻譯到 SSAS 的就是
對比一下最終在 SSRS 上的報表,相當於把 MDX Query 的結果給扁平化了。
要注意的是 STRTOSET('[Product].[Category].[Bikes]', CONSTRAINED),它將一個字符串轉化成了一個具體的集合,並且加上了 CONSTRAINED 那么在第一個參數字符串中就只能指定具體的層次結構中的成員,它有一個限定作用。
比如說如果想直接寫上全部成員就會發生這樣的錯誤。
The restrictions imposed by the CONSTRAINED flag in the STRTOSET function were violated.
如果去掉 CONSTRAINED,那么結果是OK的,類似的函數還有 STRTOMEMBER.
至於 STRTOSET 和 STRTOMEMBER 這兩個 MDX 函數就不在這篇 SSRS 博客中詳細解釋了,這里只是通過例子對比大概描述了 MDX 查詢參數化的過程和在 SSRS 報表上的使用。這種方式非常直接也比較簡單,當然也還有其它傳參數的方式,比如編寫 Expression 等等在不同的場景下也會使用到。
沒有寫好的地方,歡迎大家積極補充和指正!
更多 BI 文章請參看 BI 系列隨筆列表 (SSIS, SSRS, SSAS, MDX, SQL Server)
如果覺得這篇文章看了對您有幫助,請幫助推薦,以方便他人在 BIWORK 博客推薦欄中快速看到這些文章。