聚合(Aggregation)
Overview
在本指南中,您可以了解如何在 MongoDB Java 驱动程序中使用聚合操作。
聚合操作处理 MongoDB 集合中的数据并返回计算结果。 MongoDB 的聚合管道是查询 API 的一部分,以数据处理管道的概念为模型。文档进入多阶段管道,该管道将文档转换为聚合结果。
可以将聚合理解为汽车工厂。 汽车工厂内有一条装配线,沿线有配备专门工具来完成特定作业的装配站,例如钻头和焊机。毛坯零件进入工厂,然后被转换并组装成成品。
聚合管道是装配线,聚合阶段是装配站,操作符表达式则是专用工具。
聚合和查找操作比较
使用 find
操作,您可以:
选择要返回哪些文档
选择要返回哪些字段
对结果进行排序
使用aggregation
操作,您可以:
执行所有
find
操作重命名字段
计算字段
汇总数据
对值进行分组
聚合操作存在一些限制,您必须牢记:
返回的文档不得违反 BSON 文档大小限制(16 兆字节)。
默认情况下,管道阶段的内存限制为 100 MB。如果需要,可使用 allowDiskUse 方法超过此限制。
实用参考资料
可运行示例
基本设置
创建一个名为 AggTour.java
的新 Java 文件并包含以下导入语句:
import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.ExplainVerbosity; import com.mongodb.client.model.Accumulators; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.Filters; import com.mongodb.client.model.Projections; import org.bson.Document; import java.util.Arrays; import java.util.List;
连接到 MongoDB 部署
public class AggTour { public static void main(String[] args) { // Replace the uri string with your MongoDB deployment's connection string String uri = "<connection string uri>"; MongoClient mongoClient = MongoClients.create(uri); MongoDatabase database = mongoClient.getDatabase("aggregation"); MongoCollection<Document> collection = database.getCollection("restaurants"); // aggregation here } }
插入数据
collection.insertMany(Arrays.asList( new Document("name", "Sun Bakery Trattoria").append("contact", new Document().append("phone", "386-555-0189").append("email", "SunBakeryTrattoria@example.org").append("location", Arrays.asList(-74.0056649, 40.7452371))).append("stars", 4).append("categories", Arrays.asList("Pizza", "Pasta", "Italian", "Coffee", "Sandwiches")), new Document("name", "Blue Bagels Grill").append("contact", new Document().append("phone", "786-555-0102").append("email", "BlueBagelsGrill@example.com").append("location", Arrays.asList(-73.92506, 40.8275556))).append("stars", 3).append("categories", Arrays.asList("Bagels", "Cookies", "Sandwiches")), new Document("name", "XYZ Bagels Restaurant").append("contact", new Document().append("phone", "435-555-0190").append("email", "XYZBagelsRestaurant@example.net").append("location", Arrays.asList(-74.0707363, 40.59321569999999))).append("stars", 4).append("categories", Arrays.asList("Bagels", "Sandwiches", "Coffee")), new Document("name", "Hot Bakery Cafe").append("contact", new Document().append("phone", "264-555-0171").append("email", "HotBakeryCafe@example.net").append("location", Arrays.asList(-73.96485799999999, 40.761899))).append("stars", 4).append("categories", Arrays.asList("Bakery", "Cafe", "Coffee", "Dessert")), new Document("name", "Green Feast Pizzeria").append("contact", new Document().append("phone", "840-555-0102").append("email", "GreenFeastPizzeria@example.com").append("location", Arrays.asList(-74.1220973, 40.6129407))).append("stars", 2).append("categories", Arrays.asList("Pizza", "Italian")), new Document("name", "ZZZ Pasta Buffet").append("contact", new Document().append("phone", "769-555-0152").append("email", "ZZZPastaBuffet@example.com").append("location", Arrays.asList(-73.9446421, 40.7253944))).append("stars", 0).append("categories", Arrays.asList("Pasta", "Italian", "Buffet", "Cafeteria")), new Document("name", "XYZ Coffee Bar").append("contact", new Document().append("phone", "644-555-0193").append("email", "XYZCoffeeBar@example.net").append("location", Arrays.asList(-74.0166091, 40.6284767))).append("stars", 5).append("categories", Arrays.asList("Coffee", "Cafe", "Bakery", "Chocolates")), new Document("name", "456 Steak Restaurant").append("contact", new Document().append("phone", "990-555-0165").append("email", "456SteakRestaurant@example.com").append("location", Arrays.asList(-73.9365108, 40.8497077))).append("stars", 0).append("categories", Arrays.asList("Steak", "Seafood")), new Document("name", "456 Cookies Shop").append("contact", new Document().append("phone", "604-555-0149").append("email", "456CookiesShop@example.org").append("location", Arrays.asList(-73.8850023, 40.7494272))).append("stars", 4).append("categories", Arrays.asList("Bakery", "Cookies", "Cake", "Coffee")), new Document("name", "XYZ Steak Buffet").append("contact", new Document().append("phone", "229-555-0197").append("email", "XYZSteakBuffet@example.org").append("location", Arrays.asList(-73.9799932, 40.7660886))).append("stars", 3).append("categories", Arrays.asList("Steak", "Salad", "Chinese")) ));
基本聚合示例
如需执行聚合,请向 MongoCollection.aggregate()
方法传递聚合阶段列表。
Java 驱动程序提供 Aggregates 助手类,该类包含用于聚合阶段的构建器。
在以下示例中,聚合管道:
使用 $match 阶段来过滤其
categories
数组字段包含Bakery
元素的文档。该示例使用Aggregates.match
来构建$match
阶段。使用 $group 阶段根据
stars
字段对匹配的文档进行分组,从而为每个不同的stars
值累积文档数。
collection.aggregate( Arrays.asList( Aggregates.match(Filters.eq("categories", "Bakery")), Aggregates.group("$stars", Accumulators.sum("count", 1)) ) // Prints the result of the aggregation operation as JSON ).forEach(doc -> System.out.println(doc.toJson()));
上述聚合应该生成以下结果:
{"_id": 4, "count": 2} {"_id": 5, "count": 1}
有关本节中提到的方法和类的详情,请参阅以下 API 文档:
解释聚合示例
要查看有关 MongoDB 如何执行您的操作的信息,请使用 AggregateIterable
类的 explain()
方法。explain()
方法返回执行计划和性能统计信息。执行计划是 MongoDB 完成操作的一种潜在方式。explain()
方法同时提供获胜计划(即 MongoDB 执行的计划)和被拒绝的计划。
您可以通过将详细程度传递给 explain()
方法来指定说明的详细程度。
下表显示了说明的所有详细级别及其预计使用案例:
详细程度 | 用例(Use Case) |
---|---|
ALL_PLANS_EXECUTIONS | 你想知道 MongoDB 会选择哪个计划来运行查询。 |
EXECUTION_STATS | 您想知道您的查询是否表现良好。 |
QUERY_PLANNER | 您的查询有问题,需要尽可能多的信息来诊断问题。 |
在以下示例中,我们将打印生成执行计划的聚合阶段的获胜计划的 JSON 表示形式:
Document explanation = collection.aggregate( Arrays.asList( Aggregates.match(Filters.eq("categories", "Bakery")), Aggregates.group("$stars", Accumulators.sum("count", 1)) ) ).explain(ExplainVerbosity.EXECUTION_STATS); List<Document> stages = explanation.get("stages", List.class); List<String> keys = Arrays.asList("queryPlanner", "winningPlan"); // Prints the JSON representation of the winning execution plans for (Document stage : stages) { Document cursorStage = stage.get("$cursor", Document.class); if (cursorStage != null) { System.out.println(cursorStage.getEmbedded(keys, Document.class).toJson()); } }
前面的代码片段应生成以下输出:
{ "stage": "PROJECTION_SIMPLE", "transformBy": {"stars": 1, "_id": 0}, "inputStage": { "stage": "COLLSCAN", "filter": { "categories": {"$eq":"bakery"}}, "direction": "forward"}}
有关本节提及主题的更多信息,请参阅以下资源:
解释输出服务器手册条目
查询计划服务器手册条目
ExplainVerbosity API 文档
解释() API 文档
AggregateIterable API 文档
聚合表达式示例
Java 驱动程序提供与 $group
一起使用的累加器表达式的构建器。您必须以 JSON 格式或兼容的文档格式声明所有其他表达式。
提示
下面任一示例中的语法都将定义一个 $arrayElemAt 表达式。
“类别”前面的$
告诉 MongoDB 这是一个字段路径,使用输入文档中的“类别”字段。
new Document("$arrayElemAt", Arrays.asList("$categories", 0))
Document.parse("{ $arrayElemAt: ['$categories', 0] }")
在以下示例中,聚合管道使用 $project
阶段和各种 Projections
来返回 name
字段和计算字段 firstCategory
,其值是 categories
字段中的第一个元素。
collection.aggregate( Arrays.asList( Aggregates.project( Projections.fields( Projections.excludeId(), Projections.include("name"), Projections.computed( "firstCategory", new Document("$arrayElemAt", Arrays.asList("$categories", 0)) ) ) ) ) ).forEach(doc -> System.out.println(doc.toJson()));
上述聚合应该生成以下结果:
{"name": "456 Cookies Shop", "firstCategory": "Bakery"} {"name": "Sun Bakery Trattoria", "firstCategory": "Pizza"} {"name": "456 Steak Restaurant", "firstCategory": "Steak"} {"name": "Blue Bagels Grill", "firstCategory": "Bagels"} {"name": "XYZ Steak Buffet", "firstCategory": "Steak"} {"name": "Hot Bakery Cafe", "firstCategory": "Bakery"} {"name": "Green Feast Pizzeria", "firstCategory": "Pizza"} {"name": "ZZZ Pasta Buffet", "firstCategory": "Pasta"} {"name": "XYZ Coffee Bar", "firstCategory": "Coffee"} {"name": "XYZ Bagels Restaurant", "firstCategory": "Bagels"}
有关本节中提到的方法和类的详情,请参阅以下 API 文档: