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

DataCube

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2018年8月24日

CPOL

8分钟阅读

viewsIcon

19538

downloadIcon

500

无服务器 OLAP 引擎的 C# 实现。

引言

本文介绍了用于处理离线数据聚合和处理的无服务器 OLAP 引擎的 C# .NET 实现。这是一个依赖于 Microsoft SQL Server 并基于该存储引擎的解决方案。未来的工作将针对 CSV 文件等平面数据源。

背景

OLAP (联机分析处理) 是一种众所周知的技术,用于对存储在数据库中的信息进行离线处理,并对这些数据进行聚合,以便按不同参数进行报告。它在以下网页上进行了说明:https://en.wikipedia.org/wiki/Online_analytical_processing。它由几个部分组成:

  • 数据提取 (E)
  • 数据转换 (T)
  • 数据加载 (L)
  • 数据聚合和处理
  • 数据报告

这个 C# .NET 动态类库是为了支持成功的数据处理和报告所需的最低要求而编写的。为了访问 Microsoft SQL Server 数据库架构,它使用 INFORMATION_SCHEMA 来创建“内存中”的数据库模型。它收集有关表、列、主键和外键以及现有参照完整性的信息。DataCube 功能已在 Microsoft 提供的 Northwind 示例数据库上进行了测试。用于填充此示例数据库的 SQL 脚本已包含在项目中。此数据库包含关于 ProductsOrdersCustomers 的一般信息,这些是正在处理的主要实体。请查看下面的 Northwind 数据库架构。

DataCube 结构

DataCube 类库包含以下逻辑层:

  • 数据库模型
  • 多维数据集模型
  • 数据查询模块

使用 Northwind 数据库作为测试用例,以下是 DataCube 库的使用方法:
(注意:必须将 DataCube.dll 作为引用添加到构建的 .NET 项目中。)

数据库建模

要创建 DataCube 对象,请使用以下构造函数:

DataCube.DataCube dataCube = new DataCube.DataCube
(@"NORTHWIND_DATACUBE", @"Server=localhost; Initial Catalog=NORTHWIND; 
Integrated Security=SSPI", @"E:\\Firma\\NORTHWIND_DataCube\\", DataCubeType.DataCubeTypeMSSS);

DataCube.DataCube 构造函数接受以下参数:

  • 数据立方体的名称 (string)
  • 数据库连接 (string)
  • 服务器存储库路径 (string)
  • 数据立方体类型 (DataCubeType 类型的枚举 - 目前唯一支持的值是 DataCubeType.DataCubeTypeMSSS)

接下来,调用以下方法来创建数据库的“内存中”模型:

dataCube.Model();

现在,数据库模型已加载到内存中,可用于进一步的数据处理。

多维数据集建模

Cube 是一个多维数组,其中包含来自数据库的预聚合信息。它由以下部分组成:

  • 维度
  • 事实
  • 层次结构

Dimension 由数据库中选定的表组成。在此示例中,我们将创建四个不同的维度,请查看下面的代码。

DataCube.Dimension dimensionCustomers = 
       new DataCube.Dimension("Customers", new string[] { "CompanyName" });
dataCube.Dimensions.Add(dimensionCustomers.Name, dimensionCustomers);
DataCube.Dimension dimensionEmployees = 
       new DataCube.Dimension("Employees", new string[] { "FirstName", "LastName" });
dataCube.Dimensions.Add(dimensionEmployees.Name, dimensionEmployees);
DataCube.Dimension dimensionProducts = 
       new DataCube.Dimension("Products", new string[] { "ProductID", "ProductName", "UnitPrice" });
dataCube.Dimensions.Add(dimensionProducts.Name, dimensionProducts);
DataCube.Dimension dimensionCategories = 
       new DataCube.Dimension("Categories", new string[] { "CategoryName" });
dataCube.Dimensions.Add(dimensionCategories.Name, dimensionCategories);

DataCube.Dimensions 对象是一个“命名集合”—— .NET Dictionary 对象类型。DataCube.Dimension 构造函数接受以下参数:

  • 维度的名称 (string)
  • 列数组 (string[])

维度名称是数据库中表的实际名称。列数组是包含同一表中列的唯一名称的数组。在前面的示例中,我们创建了四个维度:CustomersEmployeesProductsCategories

Fact 也由数据库中选定的表组成。在此示例中,我们将只创建一个事实,请查看下面的代码。

DataCube.Fact fact = new DataCube.Fact("Order Details");
DataCube.Measure measureQuantity = 
      new DataCube.Measure("Quantity", DataCubeFactAggregationType.DataCubeFactAggregationTypeSum);
fact.Measures.Add(measureQuantity.Name, measureQuantity);
DataCube.DerivedMeasure measureSales = 
      new DataCube.DerivedMeasure("Sales", DataCubeFactAggregationType.DataCubeFactAggregationTypeSum, 
                                  "UnitPrice * Quantity * (1 - Discount)");
fact.DerivedMeasures.Add(measureSales.Name, measureSales);
dataCube.Facts.Add(fact.Name, fact);

DataCube.Fact 对象是在事务表“Order Details”上创建的。此数据库表跟踪售予 CustomersProducts(属于不同 Categories),由 Employees 销售。每个 DataCube.Fact 对象都有不同的 DataCube.Measure 对象,这些对象将在后续步骤中进行处理和计算。由于“Order Details”表具有 QuantityUnitPriceDiscount 列,因此我们创建了所需的确切度量。

DataCube.Measure 对象构造函数具有以下参数:

  • 度量名称 (string)
  • 数据聚合类型 (DataCubeFactAggregationType 类型的枚举)

支持五种基本聚合函数:SUMCOUNTMINMAXAVG

如果需要定义一个预计算列,就像我们这里需要一个 Sales 列一样,我们应该创建一个 DataCube.DerivedMeasure 对象。其构造函数中的附加参数是从表达式用于计算其值,就像这里 Sales = UnitPrice * Quantity * (1 - Discount) 一样。

对于基本数据处理,大部分工作在此完成。

处理多维数据集

为了获得所需的数据处理结果,我们需要对数据进行聚合和处理。请查看下面的代码。

dataCube.Process();

作为上一步的输出,存储库将填充相应的 XML 文件,这些文件代表处理后的数据。

这是生成的 XML 文件 Categories.xml 的内部结构:

此 XML 文件与数据一起编写了架构。数据包含在标签名称与维度名称匹配的元素中。它可以轻松加载到 .NET DataTable 对象中以供后续处理。数据的原始类型(列数据类型)保存在 XML 架构中。所有处理过的维度都会生成相同的输出文件。此外,Fact 中的数据会为每个单独的 Dimension 值进行聚合和计算。

这是自动生成的实际 SQL 查询:

这是执行上述 SQL 查询的结果:

此时,我们可能需要保存创建的 Cube 以供以后处理。

保存多维数据集

要保存 Cube,请查看下面的代码。

dataCube.Save();

此操作将在存储库中生成一个名为 Model.xml 的 XML 文件。这是该文件的内部结构:

此文件跟踪在 DataCube 对象中创建的维度和事实。一旦 Cube 被保存,在其维度和度量处理之后,就可以离线重新加载。不再需要连接数据库。

加载多维数据集

要加载 Cube,请查看以下代码:

DataCube.DataCube dataCube = new DataCube.DataCube("NORTHWIND_DATACUBE", null, 
                  "E:\\Firma\\NORTHWIND_DataCube\\", DataCubeType.DataCubeTypeMSSS);
dataCube.Load();

此构造函数已经解释过,但这一次,重要的是要看到不再提供数据库连接字符串。Load() 方法将从存储库加载 Cube,前提是存在 Model.xml 文件。

查询多维数据集

为了执行数据分析,我们将不得不查询由 DataCube 对象处理的数据。DataCube.Query 类就是为此目的而编写的。我在这里尝试模仿 MDX 查询逻辑,并提供实际的数据透视,以便像 Microsoft Excel 这样的 OLAP 客户端一样表示数据。因此,从这一点开始,我们正在处理和分析存储在由维度和事实处理生成的可执行 XML 输出文件中的离线数据。请查看下面的代码。

DataCube.Query query = new DataCube.Query("Test", new string[] 
    { "Categories.Categories_CategoryName" }, null, new string[] { "Quantity", "Sales" }, 
      "Categories_CategoryName IN ('Produce', 'Seafood')");

DataCube.Query 对象构造函数具有以下参数:

  • 查询名称 (string)
  • 行维度数组 (string[])
  • 列维度数组 (string[])
  • 度量数组 (string[])
  • 条件 (string)

在这里,我们实际上定义了要收集的数据、收集数据的标准以及要执行的计算。维度可以放在行或列上。度量是从已处理的维度文件输出中预先计算的值,条件只是此数据的过滤标准。要实际执行 Query,请使用以下代码:

dataCube.Execute();

如果我们对先前创建和处理的 Cube 运行 Execute() 方法,将会在存储库中生成以下 XML 输出文件,名为 Test.xml

正如输出文件所示,我们已经获得了针对特定过滤条件('Produce' 和 'Seafood')的 Categories 维度数据。

另一个更复杂的示例是获取 [Products Sales By Year] 数据。这里我们需要启用所谓的“时间层次结构”。请检查以下代码:

// Model the database
DataCube.DataCube dataCube = new DataCube.DataCube
(@"NORTHWIND_DATACUBE", @"Server=localhost; Initial Catalog=NORTHWIND; 
Integrated Security=SSPI", @"E:\\Firma\\NORTHWIND_DataCube\\", DataCubeType.DataCubeTypeMSSS);
dataCube.Model();

// Model the cube
DataCube.Fact fact = new DataCube.Fact("Order Details");
DataCube.Measure measureQuantity = new DataCube.Measure
("Quantity", DataCubeFactAggregationType.DataCubeFactAggregationTypeSum);
fact.Measures.Add(measureQuantity.Name, measureQuantity);
DataCube.DerivedMeasure measureSales = new DataCube.DerivedMeasure
("Sales", DataCubeFactAggregationType.DataCubeFactAggregationTypeSum, 
"UnitPrice * Quantity * (1 - Discount)");
fact.DerivedMeasures.Add(measureSales.Name, measureSales);
dataCube.Facts.Add(fact.Name, fact);
DataCube.Dimension dimensionProducts = new DataCube.Dimension
("Products", new string[] { "ProductID", "ProductName", "UnitPrice" });
dataCube.Dimensions.Add(dimensionProducts.Name, dimensionProducts);
DataCube.Dimension dimensionOrders = new DataCube.Dimension
("Orders", new string[] { "OrderID", "ShippedDate" });
dataCube.Dimensions.Add(dimensionOrders.Name, dimensionOrders);
DataCube.DimensionGroup dimensionGroupProductsOrders = 
new DataCube.DimensionGroup("Products_Orders", new string[] { "Products", "Orders" });
DataCube.DimensionGroupHierarchy hierarchyProductsOrdersYear = 
new DataCube.DimensionGroupHierarchy("Products_Orders_ShippedDate_YEAR", 
"Orders", "ShippedDate", DataCubeDimensionHierarchyType.DataCubeDimensionHierarchyTypeYear, 
null, new string[] { "Products.ProductName", "Orders.ShippedDate" });
dimensionGroupProductsOrders.Hierarchies.Add(hierarchyProductsOrdersYear.Name, 
hierarchyProductsOrdersYear);
dataCube.DimensionGroups.Add(dimensionGroupProductsOrders.Name, dimensionGroupProductsOrders);

// Process the cube
dataCube.Process();

// Save the cube
dataCube.Save();

在这里,我们创建了必要的维度(来自“Products”和“Orders”表),以及事实(来自“Order Details”表)以及相应的度量(QuantitySales)。接下来创建一个“维度组”。DataCube.DimensionGroup 对象的构造函数接受以下参数:

  • 组名称 (string)
  • 组中的维度数组 (string[])

处理创建的组会生成以下 SQL 查询:

并且获得的结果如下:

此组应按年处理,因此我们使用 DataCube.DimensionGroupHierarchy 类为此组创建时间层次结构。请检查此对象的构造函数参数:

  • 层次结构名称 (string)
  • 表名 (string)
  • 列名 (string)
  • 层次结构类型 (DataCubeDimensionHierarchyType 类型的枚举)
  • 父层次结构对象 (DataCube.DimensionGroupHierarchy 类型)
  • 表和对应列的数组 (string[])

使用基于年的层次结构处理创建的组会生成以下 SQL 查询:

此外,我们得到的结果是:

接下来,我们应该创建一个 Query 对象并执行它,所以请检查以下代码:

// Load the cube
DataCube.DataCube dataCube = new DataCube.DataCube
("NORTHWIND_DATACUBE", null, "E:\\Firma\\NORTHWIND_DataCube\\", DataCubeType.DataCubeTypeMSSS);
dataCube.Load();

// Model the queries
DataCube.Query query = new DataCube.Query
("Test", new string[] { "Products.Products_ProductName" }, new string[] 
{ "Orders.Orders_ShippedDate_YEAR" }, new string[] { "Quantity", "Sales" }, 
"Orders_ShippedDate_YEAR IN ('1996', '1997', '1998') 
AND Products_ProductName IN ('Chai', 'Chocolade')");
dataCube.Queries.Add(query.Name, query);

// Execute the queries
dataCube.Execute();

最后,在存储库中生成了以下 XML 输出文件:

在此,应注意支持多级数据透视,包括行和列。

另外,请检查提供的测试项目中的示例,以了解生成的 XML 文件等的正确命名约定。

关注点

对我来说,这是一个非常有挑战性的项目,我相信有人会发现这个类库很有用。

历史

  • DataCube v1.0 - 2018 年 8 月
© . All rights reserved.