在 MongoDB 中,$group 是聚合管道中的一个阶段,用于根据指定的条件对文档进行分组。
{
$group: {
_id: <expression>, // 分组的依据,可以是字段名或表达式
<field1>: { <accumulator1> : <expression1> }, // 对字段进行聚合操作
<field2>: { <accumulator2> : <expression2> },
...
}
}
_id
字段必填,指定了分组的键。如果指定的 _id
值为空值或任何其他常量值,$group
阶段将返回聚合所有输入文档值的单个文档。
<field1>
是要输出的字段名,可以是现有字段或新的计算字段。
<accumulator1>
是聚合操作符,用于对分组内的文档进行计算。
<expression1>
是要应用于每个分组的表达式,用于计算聚合操作的结果。
以下是一些常用的聚合操作符:
$sum:计算指定字段的总和。
$avg:计算指定字段的平均值。
$min:计算指定字段的最小值。
$max:计算指定字段的最大值。
$push:将指定字段的值添加到数组中。
$addToSet:将指定字段的唯一值添加到集合中。
$first:返回每个分组中指定字段的第一个值。
$last:返回每个分组中指定字段的最后一个值。
1. 构造测试数据
db.sales.drop()
db.sales.insertMany([
{
"_id": 1,
"item": "abc",
"price": Decimal128("10"),
"quantity": Int32("2"),
"date": "2014-03-01"
},
{
"_id": 2,
"item": "jkl",
"price": Decimal128("20"),
"quantity": Int32("1"),
"date": "2014-03-01"
},
{
"_id": 3,
"item": "xyz",
"price": Decimal128("5"),
"quantity": Int32("10"),
"date": "2014-03-15"
},
{
"_id": 4,
"item": "xyz",
"price": Decimal128("5"),
"quantity": Int32("20"),
"date": "2014-04-04"
},
{
"_id": 5,
"item": "abc",
"price": Decimal128("10"),
"quantity": Int32("10"),
"date": "2014-04-04"
},
{
"_id": 6,
"item": "def",
"price": Decimal128("7.5"),
"quantity": Int32("5"),
"date": "2015-06-04"
},
{
"_id": 7,
"item": "def",
"price": Decimal128("7.5"),
"quantity": Int32("10"),
"date": "2015-09-10"
},
{
"_id": 8,
"item": "abc",
"price": Decimal128("10"),
"quantity": Int32("5"),
"date": "2016-02-06"
}
])
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "sales")
public class Sales {
private ObjectId id;
private String item;
private Decimal128 price;
private int quantity;
private Date date;
}
1. 示例 1
按 item 字段对文档进行分组
db.sales.aggregate([
{ $group: { _id: "$item" } }
])
{ "_id" : "abc" }
{ "_id" : "jkl" }
{ "_id" : "def" }
{ "_id" : "xyz" }
SpringBoot 整合 MongoDB实现上述操作:
@Data
@Document(collection = "sales")
public class Sales {
@Id
private String id;
private String item;
private Decimal128 price;
private int quantity;
private Date date;
}
@Data
public class AggregationResult {
private String id;
}
@Test
public void aggregateTest() {
GroupOperation groupOperation = Aggregation.group("item");
Aggregation aggregation = Aggregation.newAggregation(groupOperation);
// aggregate():参数的顺序为聚合管道的定义、输入类型、输出类型
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Sales.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=xyz)
//AggregationResult(id=jkl)
//AggregationResult(id=def)
//AggregationResult(id=abc)
}
2. 示例2
$group 阶段按 item
对文档进行分组,并计算返回每个一项的总销售额totalSaleAmount
db.sales.aggregate(
[
// First Stage
{
$group :
{
_id : "$item",
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
}
}
]
)
// 1
{
"_id": "xyz",
"totalSaleAmount": Decimal128("150")
}
// 2
{
"_id": "jkl",
"totalSaleAmount": Decimal128("20")
}
// 3
{
"_id": "def",
"totalSaleAmount": Decimal128("112.5")
}
// 4
{
"_id": "abc",
"totalSaleAmount": Decimal128("170")
}
SpringBoot 整合 MongoDB实现上述操作:
@Data
@Document(collection = "sales")
public class Sales {
@Id
private String id;
private String item;
private Decimal128 price;
private int quantity;
private Date date;
}
@Data
public class AggregationResult {
private String id;
private Decimal128 totalSaleAmount;
}
@Test
public void aggregateTest() {
GroupOperation groupOperation = Aggregation
.group("item")
.sum(ArithmeticOperators.Multiply.valueOf("price").multiplyBy("quantity"))
.as("totalSaleAmount");
Aggregation aggregation = Aggregation.newAggregation(groupOperation);
// aggregate():参数的顺序为聚合管道的定义、输入类型、输出类型
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Sales.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=xyz, totalSaleAmount=150)
//AggregationResult(id=jkl, totalSaleAmount=20)
//AggregationResult(id=def, totalSaleAmount=112.5)
//AggregationResult(id=abc, totalSaleAmount=170)
}
3. 示例3
第一个阶段:
$group
阶段按 item
对文档进行分组,以检索非重复的项值。此阶段返回每一项的 totalSaleAmount
。
第二个阶段:
$match
阶段会对生成的文档进行筛选,从而只返回 totalSaleAmount
大于或等于 100 的项目。
db.sales.aggregate(
[
// First Stage
{
$group :
{
_id : "$item",
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
}
},
// Second Stage
{
$match: { "totalSaleAmount": { $gte: 100 } }
}
]
)
// 1
{
"_id": "xyz",
"totalSaleAmount": Decimal128("150")
}
// 2
{
"_id": "def",
"totalSaleAmount": Decimal128("112.5")
}
// 3
{
"_id": "abc",
"totalSaleAmount": Decimal128("170")
}
这个聚合操作相当于以下 SQL 语句:
SELECT item,
Sum(( price * quantity )) AS totalSaleAmount
FROM sales
GROUP BY item
HAVING totalSaleAmount >= 100
SpringBoot 整合 MongoDB实现上述操作:
@Data
public class AggregationResult {
private String id;
private Decimal128 totalSaleAmount;
}
@Test
public void aggregateTest() {
// 第一个阶段
GroupOperation groupOperation = Aggregation
.group("item")
.sum(ArithmeticOperators.Multiply.valueOf("price").multiplyBy("quantity"))
.as("totalSaleAmount");
// 第二个阶段
MatchOperation matchOperation = Aggregation.match(Criteria.where("totalSaleAmount").gte(100));
Aggregation aggregation = Aggregation.newAggregation(groupOperation,matchOperation);
// aggregate():参数的顺序为聚合管道的定义、输入类型、输出类型
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Sales.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=xyz, totalSaleAmount=150)
//AggregationResult(id=def, totalSaleAmount=112.5)
//AggregationResult(id=abc, totalSaleAmount=170)
}
4. 示例4
计算 2014 年每一天的总销售额、平均销售数量和销售数量:
第一个阶段:
$match
阶段会对这些文档进行筛选,仅将从 2014 年开始的文档传递到下一阶段。
第二个阶段:
$group
阶段按日期对文档分组,并计算每组文档的总销售金额、平均数量和总数。
第三个阶段:
$sort
阶段按每个组的总销售金额对结果进行降序排序。
db.sales.aggregate([
// First Stage
{
$match : { "date": { $gte: "2014-01-01", $lt: "2015-01-01" } }
},
// Second Stage
{
$group : {
_id : "$date",
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
averageQuantity: { $avg: "$quantity" },
count: { $sum: 1 }
}
},
// Third Stage
{
$sort : { totalSaleAmount: -1 }
}
])
// 1
{
"_id": "2014-04-04",
"totalSaleAmount": Decimal128("200"),
"averageQuantity": 15,
"count": 2
}
// 2
{
"_id": "2014-03-15",
"totalSaleAmount": Decimal128("50"),
"averageQuantity": 10,
"count": 1
}
// 3
{
"_id": "2014-03-01",
"totalSaleAmount": Decimal128("40"),
"averageQuantity": 1.5,
"count": 2
}
这个聚合操作相当于以下 SQL 语句:
SELECT date,
Sum(( price * quantity )) AS totalSaleAmount,
Avg(quantity) AS averageQuantity,
Count(*) AS Count
FROM sales
WHERE date >= '01/01/2014' AND date < '01/01/2015'
GROUP BY date
ORDER BY totalSaleAmount DESC
SpringBoot 整合 MongoDB实现上述操作:
@Data
public class AggregationResult {
private String id;
private Decimal128 totalSaleAmount;
private Double averageQuantity;
private Integer count;
}
@Test
public void aggregateTest() {
// 第一个阶段
MatchOperation matchOperation = Aggregation.match(
Criteria.where("date").gte("2014-01-01").lte("2015-01-01")
);
// 第二个阶段
GroupOperation groupOperation = Aggregation
.group("date")
.sum(ArithmeticOperators.Multiply.valueOf("price").multiplyBy("quantity")).as("totalSaleAmount")
.avg("quantity").as("averageQuantity")
.count().as("count");
// 第三个阶段
SortOperation sortOperation = Aggregation.sort(Sort.by(Sort.Direction.DESC, "totalSaleAmount"));
// 组合上面的3个阶段
Aggregation aggregation = Aggregation.newAggregation(matchOperation,groupOperation,sortOperation);
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Sales.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=2014-04-04, totalSaleAmount=200, averageQuantity=15.0, count=2)
//AggregationResult(id=2014-03-15, totalSaleAmount=50, averageQuantity=10.0, count=1)
//AggregationResult(id=2014-03-01, totalSaleAmount=40, averageQuantity=1.5, count=2)
}
5. 示例5
聚合操作指定了 null
的 _id
组,计算集合中所有文档的总销售额、平均数量和计数。
db.sales.aggregate([
{
$group : {
_id : null,
totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
averageQuantity: { $avg: "$quantity" },
count: { $sum: 1 }
}
}
])
{
"_id" : null,
"totalSaleAmount" : Decimal128("452.5"),
"averageQuantity" : 7.875,
"count" : 8
}
这个聚合操作相当于以下 SQL 语句:
SELECT Sum(price * quantity) AS totalSaleAmount,
Avg(quantity) AS averageQuantity,
Count(*) AS Count
FROM sales
SpringBoot 整合 MongoDB实现上述操作:
@Data
public class AggregationResult {
private String id;
private Decimal128 totalSaleAmount;
private Double averageQuantity;
private Integer count;
}
@Test
public void aggregateTest() {
GroupOperation groupOperation = Aggregation
.group()
.sum(ArithmeticOperators.Multiply.valueOf("price").multiplyBy("quantity")).as("totalSaleAmount")
.avg("quantity").as("averageQuantity")
.count().as("count");
// 组合上面的3个阶段
Aggregation aggregation = Aggregation.newAggregation(groupOperation);
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Sales.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=null, totalSaleAmount=452.5, averageQuantity=7.875, count=8)
}
2. 构造测试数据
db.oredrs.insertMany([
{ orderId: 1, customerId: 1, amount: 100 },
{ orderId: 2, customerId: 2, amount: 200 },
{ orderId: 3, customerId: 1, amount: 150 },
{ orderId: 4, customerId: 3, amount: 300 },
{ orderId: 5, customerId: 2, amount: 250 }
])
@Data
@Document(collection = "orders")
public class Order {
private int orderId;
private int customerId;
private double amount;
}
1. 示例1
计算每个客户的订单总额:group阶段根据 customerId 字段对订单文档进行分组,然后使用 sum 操作符计算每个客户的订单总额。
db.orders.aggregate([
{
$group: {
_id: "$customerId",
totalAmount: { $sum: "$amount" }
}
}
])
// 1
{
"_id": 3,
"totalAmount": 600
}
// 2
{
"_id": 2,
"totalAmount": 900
}
// 3
{
"_id": 1,
"totalAmount": 500
}
SpringBoot 整合 MongoDB实现上述操作:
@Data
public class AggregationResult {
private String id;
private Integer totalAmount;
}
@Test
public void aggregateTest() {
GroupOperation groupOperation = Aggregation.group("customerId")
.sum("amount").as("totalAmount");
// 组合上面的3个阶段
Aggregation aggregation = Aggregation.newAggregation(groupOperation);
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Order.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=3.0, totalAmount=300)
//AggregationResult(id=2.0, totalAmount=450)
//AggregationResult(id=1.0, totalAmount=250)
}
2. 示例2
计算每个客户的订单数量和平均订单金额:group阶段根据 customerId 字段对订单文档进行分组,然后使用 $sum 操作符计算每个客户的订单数量,并使用 $avg 操作符计算每个客户的平均订单金额。
db.orders.aggregate([
{
$group: {
_id: "$customerId",
count: { $sum: 1 },
averageAmount: { $avg: "$amount" }
}
}
])
// 1
{
"_id": 3,
"count": 1,
"averageAmount": 300
}
// 2
{
"_id": 2,
"count": 2,
"averageAmount": 225
}
// 3
{
"_id": 1,
"count": 2,
"averageAmount": 125
}
SpringBoot 整合 MongoDB实现上述操作:
@Data
public class AggregationResult {
private String id;
private Integer count;
private Integer averageAmount;
}
@Test
public void aggregateTest() {
GroupOperation groupOperation = Aggregation.group("customerId")
.count().as("count")
.avg("amount").as("averageAmount");
// 组合上面的3个阶段
Aggregation aggregation = Aggregation.newAggregation(groupOperation);
AggregationResults<AggregationResult> results
= mongoTemplate.aggregate(aggregation, Order.class, AggregationResult.class);
List<AggregationResult> mappedResults = results.getMappedResults();
mappedResults.forEach(System.out::println);
//AggregationResult(id=3.0, count=1, averageAmount=300)
//AggregationResult(id=2.0, count=2, averageAmount=225)
//AggregationResult(id=1.0, count=2, averageAmount=125)
}
3. 示例3
按照订单金额范围统计订单数量:group阶段使用 $switch
操作符根据订单金额对订单文档进行分组。根据订单金额是否小于200,将订单分为"小额订单"和"大额订单"两个组。然后使用$sum操作符计算每个组的订单数量。
db.orders.aggregate([
{
$group: {
_id: {
$switch: {
branches: [
{ case: { $lt: ["$amount", 200] }, then: "小额订单" },
{ case: { $gte: ["$amount", 200] }, then: "大额订单" }
],
default: "其他"
}
},
count: { $sum: 1 }
}
}
])
// 1
{
"_id": "大额订单",
"count": 3
}
// 2
{
"_id": "小额订单",
"count": 2
}