65.9K
CodeProject 正在变化。 阅读更多。
Home

在 NSimpleOlap 上探索数据立方体 (Alpha)

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2021 年 5 月 3 日

CPOL

6分钟阅读

viewsIcon

3651

NSimpleOlap 库的当前特性

NSimpleOlap 是一个 OLAP 引擎,更准确地说是一个内存中的 OLAP 引擎,它旨在用于教育用途和应用程序开发。它提供了一个易于使用的 API,可以轻松进行设置和查询,我希望这能更容易地展示 OLAP 引擎在解决某些类问题上的实用性。

该库将按原样提供,并且没有前端,它不适用于金融、花哨的仪表板或常规的商业智能用例。在我看来,过度宣传和过高的许可费用限制了这些系统的使用范围,并削弱了它们的接受度。将其使用限制在通常只供业务经理查看的BI孤岛中。

但它还可以更多...

从演示应用程序开始

我将从演示应用程序开始,它便于演示和探索查询数据立方体所需的主要概念。这是一个简单的控制台应用程序,对于那些更注重图形的人来说,这是一种怀旧,但对于本文的目的来说已经足够了。

您可以通过此链接找到演示应用程序和 NSimpleOlap

此演示应用程序中的种子数据是一个非常基础的平面文件 CSV 文件,尽管基本维度被引用为数字 ID,这些 ID 在支持的 CSV 文件中具有对应的项。

category, gender, place, Date, expenses, items
1, 1, 2, 2021-01-15,1000.12, 30
2, 2, 2, 2021-03-05,200.50, 5
4, 2, 5, 2021-10-17,11500.00, 101
3, 2, 2, 2021-08-25,100.00, 20
2, 1, 6, 2021-02-27,10.10, 5
1, 2, 2, 2021-08-30,700.10, 36
5, 2, 5, 2021-12-15,100.40, 31
1, 1, 3, 2021-09-07,100.12, 12
3, 2, 3, 2021-06-01,10.12, 30
2, 2, 2, 2021-06-05,10000.12, 30
1, 2, 1, 2021-05-04,100.12, 1
4, 2, 2, 2021-01-03,10.12, 6
2, 2, 3, 2021-11-09,100.12, 44
1, 2, 3, 2021-07-01,10.12, 8
4, 1, 1, 2021-04-24,100.12, 5
1, 1, 6, 2021-06-02,10.12, 7
4, 3, 6, 2021-05-18,100.12, 30
2, 1, 2, 2021-08-21,60.99, 8
1, 2, 2, 2021-02-16,6000.00, 89
4, 3, 6, 2021-03-07,600.00, 75
1, 1, 6, 2021-01-01,10.00, 12
4, 2, 2, 2021-07-28,2000.00, 30
5, 2, 6, 2021-12-20,50.10, 11
3, 1, 3, 2021-06-08,130.50, 2

执行演示应用程序将显示以下初始控制台消息。

您可以键入 `help` 以获取有关如何进行简单查询、获取可用维度和度量的基本示例。

您可以键入一个简单的查询,然后按 Enter 键获取结果。

正如您所见,结果不是按时间顺序排序的,而是按照查询引擎获取单元格的顺序排列的。一旦实现了排序选择,这个问题就会得到解决。

这是另一个例子

再举一个例子,但现在关注没有性别数据的记录。

正如您所见,一些输出 显示了许多空白字符 ,因为测试数据量不大。因此,在所有可用聚合的空间方面,当前的数据立方体非常稀疏。但是您仍然可以通过不同的视角查看数据,并了解其可能性。

开始构建自己的立方体

在这个开发阶段,您可以定义维度、度量和指标。您可以定义定义属性或实体列表(颜色、性别、城市、国家/地区等)的常规维度,或者定义需要以不同方式处理的日期维度。因为这些遵循定义的日历模式,并且需要从事实表中的传入数据生成。

度量是事实表中定义的实体观察到的变量,这些变量可以是销售或购买商品的数量、商品的值或价格、发票的总值、温度、降雨量等。这些将在立方体中以各种组合进行聚合,尽管这会带来一定程度的上下文丢失。因为由多个数据点产生的聚合单元格无法很好地说明输入数据的模式。但立方体是为了探索森林,而不是为了个别的树。

指标是在聚合时计算的表达式,这些表达式允许进行一些额外的计算,并在单元格中保留一些额外的上下文数据。这些计算值可以是平均值、最小值和最大值,或者使用当前实现的运算组合而成的任何其他表达式。

设置常规维度

添加新维度时,需要先设置事实数据源。在此特定示例中,我们需要指定一个 CSV 文件,并添加我们想要作为立方体来源的文件中的字段。此外,您还需要指定具有维度成员的数据源。该数据源将具有用作 ID 的列和用作维度成员名称的列。

CubeBuilder builder = new CubeBuilder();

builder.AddDataSource(dsbuild =>
        {
          dsbuild.SetName("sales")
            .SetSourceType(DataSourceType.CSV)
            .SetCSVConfig(csvbuild =>
            {
              csvbuild.SetFilePath("TestData//facts.csv")
                              .SetHasHeader();
            })
            .AddField("category", 0, typeof(int));
        })
        .AddDataSource(dsbuild =>
        {
          dsbuild.SetName("categories")
            .SetSourceType(DataSourceType.CSV)
            .AddField("id", 0, typeof(int))
            .AddField("description", 1, typeof(string))
            .SetCSVConfig(csvbuild =>
            {
              csvbuild.SetFilePath("TestData//dimension1.csv")
                              .SetHasHeader();
            });
        });

然后,您需要将事实数据源中的列与立方体维度进行映射。

builder.SetSourceMappings((sourcebuild) =>
        {
          sourcebuild.SetSource("sales")
            .AddMapping("category", "category");
        })

然后添加维度成员数据源的元数据映射。

builder.MetaData(mbuild =>
        {
          mbuild.AddDimension("category", (dimbuild) =>
          {
            dimbuild.Source("categories")
              .ValueField("id")
              .DescField("description");
          });
        });

设置度量

将度量添加到立方体只需要两个步骤,首先映射事实数据源中的度量列。

builder.AddDataSource(dsbuild =>
        {
          dsbuild.SetName("sales")
            .SetSourceType(DataSourceType.CSV)
            .SetCSVConfig(csvbuild =>
            {
              csvbuild.SetFilePath("TestData//tableWithDate.csv")
                              .SetHasHeader();
            })
            .AddField("category", 0, typeof(int))
            .AddField("expenses", 4, typeof(double));
        })

然后为立方体添加度量元数据映射。

builder.MetaData(mbuild =>
        {
          mbuild.AddDimension("category", (dimbuild) =>
          {
            dimbuild.Source("categories")
              .ValueField("id")
              .DescField("description");
          })
          .AddMeasure("spent", mesbuild =>
          {
            mesbuild.ValueField("expenses")
              .SetType(typeof(double));
          });
        });

设置日期维度

添加 Date 维度将增加一层复杂性,因为您需要指定数据要切片成的 Date 级别类型。

您将从映射 Date 字段开始,并在本例中指定 CSV 文件上的日期时间格式。

builder.AddDataSource(dsbuild =>
        {
          dsbuild.SetName("sales")
            .SetSourceType(DataSourceType.CSV)
            .SetCSVConfig(csvbuild =>
            {
              csvbuild.SetFilePath("TestData//tableWithDate.csv")
                              .SetHasHeader();
            })
            .AddField("category", 0, typeof(int))
            .AddDateField("date", 3, "yyyy-MM-dd")
            .AddField("expenses", 4, typeof(double));
        });

添加与数据源的映射,并指明您想要的标签字段。

builder.SetSourceMappings((sourcebuild) =>
        {
          sourcebuild.SetSource("sales")
            .AddMapping("category", "category")
            .AddMapping("date", "Year", "Month", "Day");
        })

在定义维度元数据时,指定维度标签和数据将被转换的信息类型。在本例中,您将有三个维度:YearMonthDay

builder.MetaData(mbuild =>
        {
          mbuild.AddDimension("category", (dimbuild) =>
          {
            dimbuild.Source("categories")
              .ValueField("id")
              .DescField("description");
          })
          .AddDimension("date", dimbuild => {
            dimbuild
            .SetToDateSource(DateTimeLevels.YEAR, DateTimeLevels.MONTH, DateTimeLevels.DAY)
            .SetLevelDimensions("Year", "Month", "Day");
          })
          .AddMeasure("spent", mesbuild =>
          {
            mesbuild.ValueField("expenses")
              .SetType(typeof(double));
          });
        });

设置指标

目前,指标只能在立方体初始化后设置,而不能在配置时设置,因为这将需要解析文本表达式。但您仍然可以使用表达式构建 API 添加指标。

设置指标需要您确定要使用的度量以及构建它所需的数学运算。作为一个简单的例子…

var cube = builder.Create<int>();
cube.Initialize();

cube.BuildMetrics()
    .Add("Add10ToQuantity", exb => exb.Expression(e => e.Set("quantity").Sum(10)))
    .Create();

这对于进一步理解数据不会有太大帮助,但这是一个开始。

为了获得更有用的表达式,您还可以组合两个度量并获得比率和比例。

cube.BuildMetrics()
    .Add("RatioSpentToQuantity", exb => 
     exb.Expression(e => e.Set("spent").Divide(ex => ex.Set("quantity").Value())))
    .Create();

或者使用一些有用的函数并保留一些来自源数据的上下文。

cube.BuildMetrics()
        .Add("AverageOnQuantity",
          exb => exb.Expression(e => e.Set("quantity").Average()))
        .Add("MaxOnQuantity",
          exb => exb.Expression(e => e.Set("quantity").Max()))
        .Add("MinOnQuantity",
          exb => exb.Expression(e => e.Set("quantity").Min()))
        .Create();

通过查询获取更多信息

如果数据立方体无法被查询,那么它就没有意义了。NSimpleOlap 的流畅查询 API 借鉴了 MDX 查询语言的许多概念。您需要熟悉将行和列指定为元组。一般来说,这与设置路径或使用 XSL 中的 xpath 或任何 DOM XML API 没有区别。您不仅在切片立方体,还在定义您想要可视化的数据层次结构。

定义一个简单的查询并将输出发送到文本控制台。

cube.Process();

var queryBuilder = cube.BuildQuery()
    .OnRows("category.All.place.Paris")
    .OnColumns("sex.All")
    .AddMeasuresOrMetrics("quantity");

var query = queryBuilder.Create();

query.StreamRows().RenderInConsole();
|                                | sex male  | sex female
    category toys,place Paris    |     12     |      8
 category furniture,place Paris  |     2      |      30
  category clothes,place Paris   |            |      44

您也可以在查询中同时选择度量和指标。

var queryBuilder = cube.BuildQuery()
    .OnColumns("sex.All")
    .AddMeasuresOrMetrics("quantity", "MaxOnQuantity", "MinOnQuantity");

var query = queryBuilder.Create();
var result = query.StreamRows().ToList();

对聚合值和事实进行过滤也是可能的。首先,我们将对聚合进行过滤。

var queryBuilder = cube.BuildQuery()
    .OnRows("category.All.place.All")
    .OnColumns("sex.All")
    .AddMeasuresOrMetrics("quantity")
    .Where(b => b.Define(x => x.Dimension("sex").NotEquals("male")));

var query = queryBuilder.Create();
var result = query.StreamRows().ToList();

然后,我们将通过过滤度量来缩小数据范围。

var queryBuilder = cube.BuildQuery()
    .OnRows("category.All.place.All")
    .OnColumns("sex.All")
    .AddMeasuresOrMetrics("quantity")
    .Where(b => b.Define(x => x.Measure("quantity").IsEquals(5)));

var query = queryBuilder.Create();
var result = query.StreamRows().ToList();

对事实进行过滤将生成一个具有更小数据集子集的数据立方体。这很有意义,因为主立方体没有事实的全部上下文,并且任何需要深入研究源事实的操作都需要生成一个新立方体来表示这些聚合。

总结…

NSimpleOlap 的核心正变得越来越稳定,现在已经可以查询复杂的维度层次结构了。但仍有很多工作要做,例如添加 Time 维度、通过元数据添加维度级别、使用转换器将度量数据转换为区间维度以能够查询年龄范围等。此外,还需要一些工作来建立一个结构,以便更好地在分层结构中呈现行和列标题。要做的事情很多,而时间却很少…

© . All rights reserved.