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
}
相关推荐
时序数据说14 分钟前
IoTDB如何解决海量数据存储难题?
大数据·数据库·物联网·时序数据库·iotdb
小楓12011 小时前
MySQL數據庫開發教學(二) 核心概念、重要指令
开发语言·数据库·mysql
花果山总钻风2 小时前
MySQL奔溃,InnoDB文件损坏修复记录
数据库·mysql·adb
TDengine (老段)3 小时前
TDengine IDMP 运维指南(管理策略)
大数据·数据库·物联网·ai·时序数据库·tdengine·涛思数据
Full Stack Developme3 小时前
PostgreSQL interval 转换为 int4 (整数)
数据库·postgresql
larance3 小时前
FastAPI + SQLAlchemy 数据库对象转字典
数据库·fastapi
哆啦A梦是一只狸猫3 小时前
SQL Server缩小日志文件.ldf的方法(适用于开发环境)
数据库·sql·sqlserver
CHEN5_024 小时前
时序数据库选型“下半场”:从性能竞赛到生态博弈,四大主流架构深度横评
数据库·人工智能·ai·架构·时序数据库
hqxstudying5 小时前
MyBatis 和 MyBatis-Plus对比
java·数据库·mysql·mybatis
DarkAthena6 小时前
AI生成技术报告:GaussDB与openGauss的HTAP功能全面对比
数据库·gaussdb