MongoDB - 聚合阶段 $group 的使用

文章目录

    • [1. 构造测试数据](#1. 构造测试数据)
      • [1. 示例 1](#1. 示例 1)
      • [2. 示例2](#2. 示例2)
      • [3. 示例3](#3. 示例3)
      • [4. 示例4](#4. 示例4)
      • [5. 示例5](#5. 示例5)
    • [2. 构造测试数据](#2. 构造测试数据)
      • [1. 示例1](#1. 示例1)
      • [2. 示例2](#2. 示例2)
      • [3. 示例3](#3. 示例3)

$group 阶段用于对文档进行分组操作,输出是每个唯一组键的一个文档。

{
 $group:
   {
     _id: <expression>, // Group key
     <field1>: { <accumulator1> : <expression1> },
     ...
   }
 }

_id 字段必填,指定了分组的键。如果指定的 _id 值为空值或任何其他常量值,$group 阶段将返回聚合所有输入文档值的单个文档。

<field1> 是要输出的字段名,可以是现有字段或新的计算字段。
<accumulator1> 是聚合操作符,用于对分组内的文档进行计算。
<expression1> 是要应用于每个分组的表达式,用于计算聚合操作的结果。

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"
}
]) 
java 复制代码
@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实现上述操作:

java 复制代码
@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;
}
java 复制代码
@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实现上述操作:

java 复制代码
@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;
}
java 复制代码
@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实现上述操作:

java 复制代码
@Data
public class AggregationResult {
    private String id;
    private Decimal128 totalSaleAmount;
}
java 复制代码
@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实现上述操作:

java 复制代码
@Data
public class AggregationResult {
    private String id;
    private Decimal128 totalSaleAmount;
    private Double averageQuantity;
    private Integer count;
}
java 复制代码
@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实现上述操作:

java 复制代码
@Data
public class AggregationResult {
    private String id;
    private Decimal128 totalSaleAmount;
    private Double averageQuantity;
    private Integer count;
}
java 复制代码
@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 }
])
java 复制代码
@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实现上述操作:

java 复制代码
@Data
public class AggregationResult {
    private String id;
    private Integer totalAmount;
}
java 复制代码
@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实现上述操作:

java 复制代码
@Data
public class AggregationResult {
    private String id;
    private Integer count;
    private Integer averageAmount;
}
java 复制代码
@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
}
相关推荐
独行soc13 分钟前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain31 分钟前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship32 分钟前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站35 分钟前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶38 分钟前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
梦想平凡2 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
TianyaOAO2 小时前
mysql的事务控制和数据库的备份和恢复
数据库·mysql
Ewen Seong3 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存
码农老起3 小时前
企业如何通过TDSQL实现高效数据库迁移与性能优化
数据库·性能优化
夏木~4 小时前
Oracle 中什么情况下 可以使用 EXISTS 替代 IN 提高查询效率
数据库·oracle