这篇文章将介绍一种类似于SQL的语言MDX。
MDX 是OLAP多维数据库的标准查询语言,被许多OLAP产品所支持,包括Microsoft Analysis Services、Oracle Essbase、IBM Cognos,MDX比SQL更适合针对多维数据结构进行查询和分析。
首先,我们在概念上介绍一下MDX与SQL的异同,然后使用一个开源OLAP多维数据库EuclidOLAP搭建演示环境,进行MDX的实际操作。
下面是一个SQL和一个MDX语句,他们都是由select、from、where等关键字构成。
SQL:
select t.revenue, t.cost
from a_fact_table t
where t.goods = 'Foodstuff'
MDX:
Select
Region.members on columns,
Date.members on rows
from [Panda Store]
where ([Goods].[Foodstuff]);
在SQL中,form关键字定义了数据的来源,它是一个二维表table,在MDX中from关键字定义的数据来源是一个多维数据结构Cube。在SQL中,where关键字对查询的数据范围进行了限制,select关键字则声明了将要被查看的表字段,在MDX中,where关键字起同样的作用,select关键字则声明了将要被查看的维度的具体信息。
在使用SQL进行数据分析的时候,数据模型通常会被设计为维度表和事实数据表,它们组成星型或雪花型结构。
将数据模型设计成星型结构的原因之一就是这种结构可以在逻辑上被理解成一个多维模型。
当基于这种星型结构进行数据分析时,将会遇到一个不易被察觉的问题。我们在构思SQL的查询逻辑时,始终都是面向关系型结构去思考,而无法以真正的多维模型的思路去思考。造成这个问题的原因是SQL的语义模型所描述的是由字段和行组成的二维表结构,它并不具备描述真正多维数据结构的能力。
MDX比SQL更加适合进行多维数据分析,MDX在语义层直接描述了由数据立方体、维度、维度成员所构成的多维数据模型结构,如果你使用MDX进行数据查询或分析,你就可以通过真正的多维结构视角去总览和洞察数据。
接下来我们通过示例对MDX进行介绍。
首先运行一个多维数据分析环境,这里我们使用开源多维数据库EuclidOLAP。
你可以在Github下载euclidolap源码并在Linux环境编译后运行,或者直接下载二进制文件,或者启动一个euclidolap docker容器。
这里我们使用docker容器快速运行一个euclidolap服务,执行下面的命令:
docker run -d --name euclidolap euclidolap/euclidolap:v0.1.4.1
这个euclidolap服务中预置了一些demo数据模型,其中一个名为Panda Store的cube表示一个连锁超市的销售数据,它关联了日期、地区和商品类型三个维度,它还有两个度量Sales和Sales Count。
示例一
首先执行一个简单的MDX,查询北美地区在2022年各个季度食品类商品的销售额数据,执行下面MDX:
Select
{Date.[2022].Q1,Date.[2022].Q2,Date.[2022].Q3,Date.[2022].Q4} on columns,
Goods.Foodstuff.children on rows
from [Panda Store]
where ([Region].[North America]);
运行下面的命令进入euclidolap docker容器:
docker exec -it euclidolap /bin/bash
在容器中使用euclidolap客户端工具:
[root@e6443agb8fr7 bin]# ./olap-cli
>>>>>> Please enter a command, exit enter the 'q' >>>>>>
将上面那条MDX查询语句复制到命令行执行,可以看到如下结果:
接下来对这个示例MDX语句进行解析,from部分指定了将要对哪个cube进行查询,where则将查询的数据范围限定在北美地区,select语句分别定义了显示结果中行位置和列位置将要展示的维度信息。这个示例MDX的查询是一个典型的切片操作。
示例二
将上面那个MDX修改一下,在where语句处将数据查询单位限制在北美地区的食品类别商品,查看2022年四个季度的销售金额和销售数量,如下所示:
Select
{Date.[2022].Q1,Date.[2022].Q2,Date.[2022].Q3,Date.[2022].Q4} on columns,
{[Measures].[Sales],[Measures].[Sales Count]} on rows
from [Panda Store]
where ([Region].[North America], Goods.Foodstuff);
执行它显示如下结果:
这个MDX的查询是一个典型的切块操作,如下图所示:
从目前的两个示例来看,切片就是在一个维度上对数据范围进行限制,切块就是在两个维度上对数据范围进行限制,将它们形象化后就是如下图所示的样子:
上述对切片和切块的类比是狭隘的,实际上切片和切块没有任何区别,它们的本质都是关注多维数据结构中的局部区域,并在此范围内进行数据分析和查询,例如,有一个关联了1000个维度的cube,针对它的一个MDX语句中对999个维度进行了限定,这个操作的本质也是和上述两个示例中的切片和切块操作是一样的。
示例三
查询2022年三季度各个地区的各种商品类别的销售额数据:
Select
Region.[ALL].children on columns,
Goods.[ALL].children on rows
from [Panda Store]
where (Date.[2022].Q3);
这条MDX语句与示例一类似,不同的是日期维度作为切片条件出现在where关键字后面,地区维度则被放置到了行位置。你可以理解为这是将示例一中的Cube进行了旋转。
示例四
使用下面这个MDX查询水果类商品以及每种具体水果的销售数据。
Select
union(Goods.Foodstuff.Fruits, Goods.Foodstuff.Fruits.children) on columns,
[Measures].Sales on rows
from [Panda Store];
从查询结果可以看出,水果类商品的销售额是由每一种具体水果销售额汇总而得出的,不过这个MDX语句中并没有像SQL那样显式调用sum函数,MDX具有对非明细数据自动汇总的能力,所以你在使用MDX查询任何粒度的数据时,不需要关心具体的汇总过程。同样,饮料、水果、肉类和酒类商品向上汇总出了食物这个商品大类的度量数据,下面是对应的MDX。
Select
union(Goods.Foodstuff, Goods.Foodstuff.children) on columns,
[Measures].Sales on rows
from [Panda Store];
示例五
MDX支持自定义计算公式,下面这个MDX计算各种具体饮料产品占整个饮料类商品销售额的百分比。
with
member [Measures].DrinkRate as [Measures].Sales / (Goods.currentmember.parent, [Measures].Sales)
Select
Goods.Foodstuff.Drink.children on columns,
{[Measures]. DrinkRate, [Measures].Sales} on rows
from [Panda Store];
在这个MDX中,我们在with关键字后自己定义了一个名为DrinkRate 的度量成员,当查询这个度量成员时,他会计算当前商品维度成员对应的销售额与其父级成员对应的销售额的比率。
示例六
MDX支持函数,下面这个MDX语句使用Children函数显示2022年中4个季度的数据。
Select
Children(Date.[2022]) on columns,
[Measures].members on rows
from [Panda Store];
上示例使用的函数Children属于集合函数, MDX还包括成员函数,数值函数,逻辑函数和字符串函数,通过与自定义计算公式的结合能够满足大部分日常的数据分析需求。
示例七
MDX支持跨Cube查询,下面这个MDX示例语句在查询第1个Cube销售额的同时,也将第2个Cube(与第1个Cube具有相同的结构,只是数据不同)的销售额查询并显示出来。
with member [Measures].otherCubeSales
as lookupcube("Dolphin Store", "[Measures].Sales")
select
Date.[2022] on rows,
{[Measures].Sales, [Measures].otherCubeSales} on columns
from [Panda Store];
这类似于SQL中的join操作,不过SQL中的join操作可能由于所关联的表数据量太大而导致查询性能显著降低,而MDX中的跨Cube操作则不会有这类问题。