基于数据仓库上的 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 等等在不同的场景下也会使用到。
没有写好的地方,欢迎大家积极补充和指正!