Mondrian是一个开放源代码的Rolap服务器,使用java开发的。它实现了xmla和jolap规范,而且自定义了一种使用mdx语言的客户端接口。Mondrian是olap服务器,而不是数据仓库服务器,因此Mondrian的元数据主要包括olap建模的元数据,不包括从外部数据源到数据库转换的元数据。也就是说Mondria的元数据仅仅包括了多维逻辑模型,从关系型数据库到多维逻辑模型的映射,存取权限等信息。在功能上,Mondrian支持共享维和成员计算,支持星型模型和雪花模型的功能。
Mondrian中使用物理的xml文件存储元数据,它的设计者规定了xml文件的格式。下面简单介绍一下它是如何存储元数据的。
Element Description
根元素
<Schema> Collection of Cubes, Virtual cubes, Shared dimensions, and Roles.
逻辑元素
<Cube> A collection of dimensions and measures, all centered on a fact table.
<VirtualCube> A cube defined by combining the dimensions and measures of one or more cubes.
<Dimension>
<DimensionUsage> Usage of a shared dimension by a cube.
<Hierarchy>
<Level>
<Property>
<Measure>
物理元素
<Table> Fact- or dimension table.
<View> Defines a 'table' using a SQL query, which can have different variants for different underlying databases.
<Join> Defines a 'table' by joining a set of queries.
存取控制
<Role> An access-control profile.
<SchemaGrant> A set of rights to a schema.
<CubeGrant> A set of rights to a cube.
<HierarchyGrant> A set of rights to a hierarchy and levels within that hierarchy.
<MemberGrant> A set of rights to a member and its children.
其他
<Parameter>
<Table>
<Table>
一个模式定义一个多维数据库,它包括一个逻辑模型,由立方体,层次,成员和逻辑模型到物理模型的映射构成。一个逻辑模型由可以用MDX语言来查询。Mondrain的模型由xml文件来描述。现在创建模式的唯一途径是用文本编辑a器编辑xml文件。Xml的语法不是太复杂,因此没有想象中的那么难。目前正在开发一个图形界面的程序来创建和修改模式。
一个模式最重要的组成部分是立方体,度量和维:在一个主题域中立方体是维和度量的集合。一个度量是一个可测量的数值,比如产品销售的数量或者详细清单的价格
一个维是一个属性或者是属性的集合, 通过维你可以将度量划分到字类中。比如:你希望将销售产品按颜色,顾客性别,售出的商店分为八个部分,那么颜色,性别,商店都是维。
下面是一个简单的模型定义的例子:
<Schema>
<Cube name="Sales">
<Table name="sales_fact_1997"/>
<Dimension name="Gender" foreignKey="customer_id">
<Hierarchy hasAll="true" allMemberName="All Genders" primaryKey="customer_id">
<Table name="customer"/>
<Level name="Gender" column="gender" uniqueMembers="true"/>
</Hierarchy>
</Dimension>
<Dimension name="Time" foreignKey="time_id">
<Hierarchy hasAll="false" primaryKey="time_id">
<Table name="time_by_day"/>
<Level name="Year" column="the_year" type="Numeric"
uniqueMembers="true"/>
<Level name="Quarter" column="quarter"
uniqueMembers="false"/>
<Level name="Month" column="month_of_year" type="Numeric"
uniqueMembers="false"/>
</Hierarchy>
</Dimension>
<Measure name="Unit Sales" column="unit_sales"
aggregator="sum" formatString="#,###"/>
<Measure name="Store Sales" column="store_sales"
aggregator="sum" formatString="#,###.##"/>
</Cube>
</Schema>
这个模型包含了一个销售cube,这个cube有两个维,时间和性别维;两个度量,销售数量和销售总额。
我们可以在这个模型上写一个 MDX 查询:
select {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,
{[Time].[1997].[Q1].descendants} on rows
from [Sales]
where [Gender].[F]
这 个查询涉及到了销售立方体, 每一个维 [Measures], [Time], [Gender], 这些维的多个成员. 结果如下:
[Time] [Measures].[Unit Sales] [Measures].[Store Sales]
[1997].[Q1] 0 0
[1997].[Q1].[Jan] 0 0
[1997].[Q1].[Feb] 0 0
[1997].[Q1].[Mar] 0 0
下面详细地介绍一下模式定义:
一个立方体是一个或者多个维和度量的集合,通常是一个事实表,这里是 ‘sales_fact_1997". 事实表保存了需要计算的列和包含维的参考表.
<Cube name="Sales">
<Table name="sales_fact_1997"/>
...
</Cube>
这里用 <Table> 元素定义事实表. 如果事实表 不在默认的模式中, 你可以用"schema"属性指定一个明确地模式,例如:
<Table schema="foodmart" name="sales_fact_1997"/>
你也可以利用 <View> 和 <Join> 结构来创建更复杂的sql .
度量
销售立方体定义了两个维 "Unit Sales" 和 "Store Sales".
<Measure name="Unit Sales" column="unit_sales"
aggregator="sum" formatString="#,###"/>
<Measure name="Store Sales" column="store_sales"
aggregator="sum" formatString="#,###.00"/>
每个度量有一个名字,对应事实表中的一列, 采用一个聚集函数 (usually "sum").
一个可选的格式字符串指定了值如何被打印. 这里我们选择销售数量不带小数的输出(因为销售数量是整数) ,销售总额带2位小数 . 符号',' 和 '.' 是对地区敏感的, 因此如果是在意大利运行, 销售总额可能会出现 "48.123,45". 你可以用 advanced format strings来实现更严格的效果.度量值不是从列中来的,而是从立方体的单元中来的
维
性别维由单一的层次组成,仅有一层。
<Dimension name="Gender" foreignKey="customer_id">
<Hierarchy hasAll="true" primaryKey="customer_id">
<Table name="customer"/>
<Level name="Gender" column="gender" uniqueMembers="true"/>
</Hierarchy>
</Dimension>
对于任意给定的销售, 性别维是指购买改产品的客户的性别. 它通过连接事实表"sales_fact_1997.customer_id"和维表"customer.customer_id"
来表示 。"gender" 包括两个值, 'F' 和 'M', 因此性别维包含的成员: [Gender].[F] and [Gender].[M]. 因为 hasAll="true", 系统产生一个特别的 'all' 层, 仅包括一个成员 [All Genders].
一个维可以包含多个层次:
<Dimension name="Time" foreignKey="time_id">
<Hierarchy hasAll="false" primaryKey="time_id">
<Table name="time_by_day"/>
<Level name="Year" column="the_year" type="Numeric"
uniqueMembers="true"/>
<Level name="Quarter" column="quarter"
uniqueMembers="false"/>
<Level name="Month" column="month_of_year" type="Numeric"
uniqueMembers="false"/>
</Hierarchy>
<Hierarchy name="Time Weekly" hasAll="false" primaryKey="time_id">
<Table name="time_by_week"/>
<Level name="Year" column="the_year" type="Numeric"
uniqueMembers="true"/>
<Level name="Week" column="week"
uniqueMembers="false"/>
<Level name="Day" column="day_of_week" type="String"
uniqueMembers="false"/>
</Hierarchy>
</Dimension>
第一个层次没有指定名称.缺省的情况下,一个层次拥有和它的维相同的名称。,因此第一个层次成为"Time".这些层次没有太多的共同之处,他们甚至没有相同的表,除非它们连接了实施表中的同一列"time_id"。在一个维上存在两个层次的原因是这样对最终用户是有用的. 如果一个维上存在两个层次, MDX会强制不允许在一个查询中同时用到他们.
A dimension can live in the fact table:
<Cube name="Sales">
<Table name="sales_fact_1997"/>
...
<Dimension name="Payment method">
<Hierarchy hasAll="true">
<Level name="Payment method" column="payment_method" uniqueMembers="true"/>
</Hierarchy>
</Dimension>
</Cube>
每个维包含有多层组成的一个层次,
大多数维都是仅有一个层次,但有时候一个维有多个层次。比如:你可能希望在时间维上从天聚集到月,季度和年;或者从天聚集到周和年。这两种层次都是从天到年,但是聚集的路径不同。大多数层次有全成员,全成员包括层次的所有成员,因此能够代表他们的总合。它通常命名为'All something',比如:'All stores'.
星型模式和雪花模式
mondrian支持星型模式和雪花模式。下面介绍一下雪花模式的建模,它需要用到操作符 <Join>.比如:
<Cube name="Sales">
...
<Dimension name="Product" foreignKey="product_id">
<Hierarchy hasAll="true" primaryKey="product_id" primaryKeyTable="product">
<Join leftKey="product_class_id" rightAlias="product_class" rightKey="product_class_id">
<Table name="product"/>
<Join leftKey="product_type_id" rightKey="product_type_id">
<Table name="product_class"/>
<Table name="product_type"/>
</Join>
</Join>
...
</Hierarchy>
</Dimension>
</Cube>
这里定义一个 "Product" 维 由三个表构成. 事实表连接 表"product" (通过外键 "product_id"),表"product"连接表"product_class" (通过外键 "product_class_id"),表"product_class"连接表 "product_type" (通过外键 "product_type_id"). 我们利用 <Join> 元素的循环嵌套, <Join>带有两个操作对象; 操作对象可能是表,连接或者查询 。
按照操作对象行的数目来安排次序,表 "product" 的行数最大, 因此它首先出现连接事实表;然后是表 "product_class"和 "product_type",在雪花的末端拥有的行数最小.
注意外部元素 <Join>有一个属性 rightAlias. 这是必要的,因为join 的右边(是内部元素 <Join> ) 有可能是许多表组成的.这种情况下不需要属性leftAlias,因为列 leftKey 很明确的来自表 "product".
共享维
当为一个连接生成SQL的时候, mondrian 需要知道连接哪一个列. 如果一正在连接一个多表连接, 你需要告诉它连接这些表里的哪一个表,哪一个列.
因为共享维不属于一个cube,你必须给它们一个明确的表 (或者数据源). 当你在一个特别的cube里用他们的时候, 你要指定外键 foreign key. 下面的例子显示了 Store Type 维被 连接到 Sales cube ,用了外键 sales_fact_1997.store_id, 并且被连接到Warehouse cube ,用了外键 warehouse.warehouse_store_id :
<Dimension name="Store Type">
<Hierarchy hasAll="true" primaryKey="store_id">
<Table name="store"/>
<Level name="Store Type" column="store_type" uniqueMembers="true"/>
</Hierarchy>
</Dimension>
<Cube name="Sales">
<Table name="sales_fact_1997"/>
...
<DimensionUsage name="Store Type" source="Store Type" foreignKey="store_id"/>
</Cube>
<Cube name="Warehouse">
<Table name="warehouse"/>
...
<DimensionUsage name="Store Type" source="Store Type" foreignKey="warehouse_store_id"/>
</Cube>
虚拟 cubes
父子层次
一个使用方便的层次 有一个严格的层的集合, 成员与层紧密的联系.比如,在 Product 层次中, 任何产品名称层的成员在商标层上都有一个父亲 ,商标层上的成员在产品子目录层也都有一个父亲. 这种结构对于现实世界中的数据有时候太严格了.
一个父子层次只有一层 (不计算 'all' 层), 但是任何成员可以在同一层上有父亲成员. 一个典型的例子是Employees 层次:
<Dimension name="Employees" foreignKey="employee_id">
<Hierarchy hasAll="true" allMemberName="All Employees" primaryKey="employee_id">
<Table name="employee"/>
<Level name="Employee Id" uniqueMembers="true" type="Numeric"
column="employee_id" nameColumn="full_name"
parentColumn="supervisor_id" nullParentValue="0">
<Property name="Marital Status" column="marital_status"/>
<Property name="Position Title" column="position_title"/>
<Property name="Gender" column="gender"/>
<Property name="Salary" column="salary"/>
<Property name="Education Level" column="education_level"/>
<Property name="Management Role" column="management_role"/>
</Level>
</Hierarchy>
</Dimension>
这里parentColumn 和nullParentValue是重要的属性:
属性parentColumn 是一个成员连接到它父亲成员的列名。在这种情况下, 它是指向雇员经理的外键。元素<Level>的子元素 <ParentExpression> 是与属性 parentColumn 有相同作用的,但是元素允许定义任意的SQL表达式, 就像元素 <Expression>. 属性 parentColumn (或者 元素<ParentExpression>) 是维一向Mondrian指出 层次有父子结构的。
属性 nullParentValue 是指明成员没有父成员的值 。 缺省情况下 nullParentValue="null", 但是因为许多数据库不支持null, 建模时 用其他值来代替空值,0和-1.
物理结构
member reade
member reader 是访问成员的方法. 层次通常以维表为基础建立的 , 因此要用sql来构造.但是甚至你的数据没有存在于 RDBMS, 你可以通过一个 Java 类来访问层次。(自定义 member reader)
Here are a couple of examples:
DateSource (to be written)生成一个时间层次. 按常规,数据仓库工具生成一个表 ,每天包含一行。但是问题是这个表需要装载,并且随着时间的变化能够添加更多的行。 DateSource 在内存中按照要求生成日期成员.
FileSystemSource (to be written) 按照目录和文件的层次描述文件系统。 Like the time hierarchy created by DateSource, this is a virtual hierarchy: the member for a particular file is only created when, and if, that file's parent directory is expanded.
ExpressionMemberReader (to be written) 创建了一个基于表达式的层次。
自定义member reader 必须实现接口 mondrian.rolap.MemberSource. 如果你需要实现一个更大的成员操作集合, 需要实现接口 interface mondrian.rolap.MemberReader; 否则, Mondrian在 mondrian.rolap.CacheMemberReader中封装 你的 reader类.你的 member reader 必须有一个公共的构造函数,这个构造函数拥有参数(Hierarchy,Properties),抛出未检查的错误.
Member readers 用 元素<Hierarchy> 的属性memberReaderClass来声明; 任何 <Parameter> 子元素通过属性构造函数来传递.
这是一个例子:
<Dimension name="Has bought dairy">
<Hierarchy hasAll="true" memberReaderClass="mondrian.rolap.HasBoughtDairySource">
<Level name="Has bought dairy" uniqueMembers="true"/>
<Parameter name="expression" value="not used"/>
</Hierarchy>
</Dimension>
Cell readers
<Measure name="name" cellReaderClass="com.foo.MyCellReader">
类 "com.foo.MyCellReader" 实现了接口interface mondrian.olap.CellReader.
存取控制
可以定义存取控制的属性(角色), 作为模式的一部分, 并且可以在建立连接的时候设置角色。
定义角色
角色可以通过 元素<Role>来设置 , 它是元素<Schema> 的直接的子元素.
下面是一个关于角色的例子:
<Role name="California manager">
<SchemaGrant access="none">
<CubeGrant cube="Sales" access="all">
<HierarchyGrant hierarchy="[Store]" access="custom" topLevel="[Store].[Store Country]">
<MemberGrant member="[Store].[USA].[CA]" access="all"/>
<MemberGrant member="[Store].[USA].[CA].[Los Angeles]" access="none"/>
</HierarchyGrant>
<HierarchyGrant hierarchy="[Customers]" access="custom" topLevel="[Customers].[State Province]" bottomLevel="[Customers].[City]">
<MemberGrant member="[Customers].[USA].[CA]" access="all"/>
<MemberGrant member="[Customers].[USA].[CA].[Los Angeles]" access="none"/>
</HierarchyGrant>
<HierarchyGrant hierarchy="[Gender]" access="none"/>
</CubeGrant>
</SchemaGrant>
</Role>
元素 <SchemaGrant> 定义了模式中缺省的对象方问权限. 访问属性可以是 "all" 或者 "none"; 这个属性可以被具体的权限对象继承. 在这个例子中, 因为 access="none", 用户只能浏览"Sales" 立方体, 这里明确的赋予了这个权限.
元素 <CubeGrant> 定义了立方体的访问权限. 就像 <SchemaGrant>, 属性access 可以是"all" 或者 "none", 并且能够被cube中具体的子对象继承.
元素 <HierarchyGrant>定义了层次的访问权限. 属性access 可以是"all", 意思是所有的members都是可见的; "none",意思是 hierarchy的存在对用户是隐藏的; "custom", 你可以利用属性 topLevel 定义可见的最高层 (阻止用户 进行上卷操作, 比如浏览税收 上卷到 Store Country 层); 或者用属性 bottomLevel 定义可见的最底层 (这里阻止用户查看顾客个人的细节数据);或者控制用户查看哪一个成员集合,通过嵌套定义元素 <MemberGrant>.
你也可以只定义元素 <MemberGrant> ,如果模式的<HierarchyGrant> 有属性access="custom". Member grants 赋予 (或者取消) 访问给定的成员, 以及它的所有子成员.
改了不少JPivot/mondrian代码,还修正了jpivot一个bug。
对JPivot的jfreechart和drillthrough显示做了增强,终于可以拿出去给人用了。
先说说性能问题: 先是找了一台闲置的IBM X445 PC Server,4×2GHZ CPU,8G内存,2×146G硬盘,操作系统 windows 2000 , 开启AWE 3G参数。然后装Oracle 10g,数据仓库模式,使用了4G AWE内存共约4.5GB内存。再建成一张1600万用户数据宽表,宽表一律使用bitmap索引,还有其他20个左右维表。 然后就简单了,写mondrian Cube,配JPivot。 最后搞下来的结果是:基本上mondrian 每次做group by 操作最长不超过30秒,一般在20秒左右。用户基本可以接受。问了使用NCR的朋友,说NCR使用自己的数据库,也基本是这样的一个性能。 PS:偷偷问一声,在这基础上,性能还能改进否?
再说说方向问题: 我们现在使用2个OLAP,一个是jpivot + mondrian ,属于ROLAP;另一个是BO intelligence + essbase,属于MOLAP。目前的感觉是,由于DB性能强悍,导致ROLAP和MOLAP在性能上相差不大。同时ROLAP可以直接和报表系统共用同一张表。而MOLAP则需要使用工具来打CUBE做数据转换,这样在开发和维护工作量上,MOLAP比ROLAP大。 另外往往业务部门分析到最后,就是要看明细数据了,这个时候MOLAP的前端工具往往不能做好支持。而jpivot则无此问题。 综上所述,我目前好像还没看到必须用MOLAP的理由,听说华为原来用M$ 的OLAP,后来好像支持不住了,就直接用回了BO 报表,呵呵。
JPivot的问题: 操作太复杂,必须对OLAP的概念有清晰的了解,普通用户无法使用。与mondrian 集成不够紧密。mondrian不提供数据钻取功能,该功能是jpivot自己做的,所以会导致数据类型格式丢失。钻取详细数据量无限制,导致内存溢出。界面比较难看,操作方式非主流使用jpivot自己的mvc框架,不易其他框架集成 总体来说,jpivot目前已经不是一个玩具了,完全可以用于企业级的操作,而且定位在高端业务分析人员。
拿出来开源比较困难,一方面jpivot在不停升级,另一方面我在修改的时候不顾一切,在jpivot中乱引用了mondrian代码,还把mondrian部分无用代码全删了。这样,我就在这个帖里把能共享部分都在这里帖出来。 首先是我优化后的界面。 1.图标用了pentaho里面的图标。 2.jpivot里面其实支持3D饼图,只是选项未开,我先将jfreechart升级成1.0.2,又对饼图、线图等做了美观。 3.drillthrough是jpivot相对其他olap产品的杀手级功能,但是有不少细节未完善。我基本都一一补上。 在界面上可以看出,我添加了一个CSV导出功能(改了WCF库),同时限制最大导出20万行记录(改了jpivot)。界面上显示的“访问次数”是measure的名字,实际上应该显示“访问时间”,该问题暂时无解。另外修正了一下numberformat、dateformat不正确的一些问题。 4.excel导出时,格式很难看,但是由于excel本身只支持256色,无法显示web上的底色,所以我修改了只显示蓝色的border,底色一律为白。 附件中rar里面是web的CSS文件、Excel的生成文件和jpivot的图表生成部分代码,感兴趣的朋友各取所需吧
另外还把jpviot完全整合到我自己的系统中去了,呵呵。 可以在系统web界面上编写Cube和MDX定义,Cube和MDX为一对多关系。Cube通过xsd来做校验。开发Cube和MDX的时候可以随时做预览。 然后再把一个MDX在界面发布成一个单独的OLAP分析。 下一步的目标是将数据权限与jpivot做整合,由于Cube的xml是由系统自动生成的,所以mondrian的role配置也可以由系统根据配置自动生成。 这部分代码涉及我的系统和框架比较深,所以不帖代码了哈,大家自己搞搞2天也就出来了
还做了个及其变态的功能,就是把界面上所有显示的jpivot cell,一个个的去取出钻取数据的measure,然后生成csv文件,打成zip包给用户下载或发到其他接口。 当时我化了整整一个礼拜钻研mondrian代码,希望可以不用那么傻傻个一个个去钻,结果失败...
我在用Jpivot的时候,发现用mondrian是影响取数性能其中的一个瓶颈........ 经研究.....我们自己修改了jpivot和wcf的一些代码来适应我们自己的项目.........以下是我做的一些修改.....想听听大家的意见
1.脱离mondrian.直接写dll的方式取数,然后生成XML数据 .我发现脱离mondrian自己写了一个DLL去调用MSSQL 2000 的OLAP,数度很快...........
2 .修改界面的显示方式 上面也说道.Jpivot的界面一个不好看,二是用起来很不方便.比如取维度等的时候....一层一层的进去实在很麻烦.... a.修改取维度的方式 我们参照ms的做法 做成一个了一个树的取数,研究jpivot里面的代码.如果直接用jpivot的代码取数据十分慢.这样我自己通过AJAX和Jpivot结合,动态生成树的结构,然后在树上取维度的时候,直接通过鼠标托到选择维度textbox上.........依照条件生成相应MDX....显示数据..... b.修改数据显示的样式.和取维度,生成MDX分开了. 显示数据我用了另外一种方式显示.就是用Frame分为上下两层.....上下两层可以通过按钮扩大整个页面........
3. 集成在自己的框架中 集成在自己的框架中,我个人觉得是比较麻烦的一件事情.一点小事没有搞好就很麻烦...因为我们是用JSF开发的.所以依照Jpivot....自己写了一些组件来辅助开发,我自己开发主要改成比较像ms 2000 的olap分析方法... 还未完成的需求 JFreeChar的功能还需要加强. 个人感觉:jpivot是很不错.可是不能一拿来就用..我发现好多人用jpivot都要修改好多东西....但是修改起来又比较麻烦....java,j2ee,xml ,xslt,javascript,taglib.....好多东西都要懂.....