MongoDB 从 3.4 版本开始,通过使用聚合框架支持分面分类(Faceted Classification)。分面分类将多个语义类别(常规的或特定的主题)组合在一起以创建完整的分类条目。凡是流经聚合管道的文档将被分类到存储桶(Buckets)中,多方面的分类支持对同一组输入文档进行各种聚合,而无需多次检索输入文档。
存储桶操作根据指定的表达式和存储桶边界将传入文档分类为组(称为存储桶)。存储桶操作需要分组字段或分组表达式。你可以使用 Aggregate 类的 bucket() 和 bucketAuto() 方法来定义它们。BucketOperation 和 BucketAutoOperation 可以根据输入文档的聚合表达式累积。您可以使用 with...() 和 andOutput(String) 方法。还可以使用 as(String) 方法为操作设置别名。每个存储桶在输出中均表示为一个文档。
BucketOperation 采用一组定义的边界将传入文档分组到这些类别中。需要对边界进行排序。以下显示了存储桶操作的一些示例:
// generates {$bucket: {groupBy: $price, boundaries: [0, 100, 400]}} bucket("price").withBoundaries(0, 100, 400); // generates {$bucket: {groupBy: $price, default: "Other" boundaries: [0, 100]}} bucket("price").withBoundaries(0, 100).withDefault("Other"); // generates {$bucket: {groupBy: $price, boundaries: [0, 100], output: { count: { $sum: 1}}}} bucket("price").withBoundaries(0, 100).andOutputCount().as("count"); // generates {$bucket: {groupBy: $price, boundaries: [0, 100], 5, output: { titles: { $push: "$title"}}} bucket("price").withBoundaries(0, 100).andOutput("title").push().as("titles");
BucketAutoOperation 确定边界,试图将文档平均分配到指定数量的桶中。BucketAutoOperation 可以选择取一个粒度值,指定使用的首选数字系列,以确保计算的边界边缘在首选的整数或10的幂上结束。下面列出了桶操作的例子:
// generates {$bucketAuto: {groupBy: $price, buckets: 5}} bucketAuto("price", 5) // generates {$bucketAuto: {groupBy: $price, buckets: 5, granularity: "E24"}} bucketAuto("price", 5).withGranularity(Granularities.E24).withDefault("Other"); // generates {$bucketAuto: {groupBy: $price, buckets: 5, output: { titles: { $push: "$title"}}} bucketAuto("price", 5).andOutput("title").push().as("titles");
为了在桶中创建输出字段,桶操作可以通过 andOutput() 使用 AggregationExpression,通过 andOutputExpression() 使用 SpEL 表达。
多个聚合管道可用于创建多方面的聚合,以表示单个聚合阶段中多个维度(或分面)的数据。多方面的聚合提供多个筛选器和分类来指导数据浏览和分析。分面的一个常见实现是,许多在线零售商通过应用产品价格、制造商、尺寸和其他因素的过滤器,提供缩小搜索结果的方法。
您可以使用聚合类的 facet() 方法定义 FacetOperation。可以使用 and() 方法使用多个聚合管道对其进行自定义。每个子管道在输出文档中都有自己的字段,其结果存储为文档数组。
子管道可以在分组之前投影和过滤输入文档。常见的用例包括在分类之前提取日期部分或进行计算。以下列表显示了方面操作示例:
// generates {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}} facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice")) // generates {$facet: {categorizedByCountry: [ { $match: { country: {$exists : true}}}, { $sortByCount: "$country"}]}} facet(match(Criteria.where("country").exists(true)), sortByCount("country")).as("categorizedByCountry")) // generates {$facet: {categorizedByYear: [ // { $project: { title: 1, publicationYear: { $year: "publicationDate"}}}, // { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}} // ]}} facet(project("title").and("publicationDate").extractYear().as("publicationYear"), bucketAuto("publicationYear", 5).andOutput("title").push().as("titles")) .as("categorizedByYear"))
按计数排序操作根据指定的表达式的值对输入的文档进行分组,计算每个不同组中的文档计数(Count),并按计数对结果进行排序。它提供了在使用分面分类时应用排序的快捷方式。按计数排序操作需要分组字段或分组表达式。
下面显示了一个按计数排序的示例:
// generates { $sortByCount: "$country" } sortByCount("country");
按次数排序的操作等同于以下BSON(二进制JSON):
{ $group: { _id: <expression>, count: { $sum: 1 } } }, { $sort: { count: -1 } }
我们通过 ProjectionOperation 和 BucketOperation 类的 andExpression() 方法支持在投影表达式中使用 SpEL 表达式。这个功能让你把所需的表达式定义为 SpEL 表达式。在运行查询时,SpEL 表达式被翻译成相应的 MongoDB 投影表达式部分,这使得表达复杂的计算变得更加容易。
考虑以下 SpEL 表达式:
1 + (q + 1) / (q - 1)
上面的表达式将被翻译成下面的 MongoDB 投影表达式:
{ "$add" : [ 1, { "$divide" : [ { "$add":["$q", 1]}, { "$subtract":[ "$q", 1]} ] }]}
下表显示了 Spring Data MongoDB 所支持的 SpEL 转换:
SpEL表达式 | Mongo表达式 |
a == b | { $eq : [$a, $b] } |
a != b | { $ne : [$a , $b] } |
a > b | { $gt : [$a, $b] } |
a >= b | { $gte : [$a, $b] } |
a < b | { $lt : [$a, $b] } |
a ⇐ b | { $lte : [$a, $b] } |
a + b | { $add : [$a, $b] } |
a - b | { $subtract : [$a, $b] } |
a * b | { $multiply : [$a, $b] } |
a / b | { $divide : [$a, $b] } |
a^b | { $pow : [$a, $b] } |
a % b | { $mod : [$a, $b] } |
a && b | { $and : [$a, $b] } |
a || b | { $or : [$a, $b] } |
!a | { $not : [$a] } |
除了上述的转换之外,你还可以使用标准的 SpEL 操作,例如使用 new 来创建数组,并通过它们的名字引用表达式。下面的例子显示了如何以这种方式创建一个数组:
// { $setEquals : [$a, [5, 8, 13] ] } .andExpression("setEquals(a, new int[]{5, 8, 13})");