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
}
相关推荐
tatasix23 分钟前
MySQL UPDATE语句执行链路解析
数据库·mysql
南城花随雪。36 分钟前
硬盘(HDD)与固态硬盘(SSD)详细解读
数据库
儿时可乖了37 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
懒是一种态度39 分钟前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
天海华兮41 分钟前
mysql 去重 补全 取出重复 变量 函数 和存储过程
数据库·mysql
gma9992 小时前
Etcd 框架
数据库·etcd
爱吃青椒不爱吃西红柿‍️2 小时前
华为ASP与CSP是什么?
服务器·前端·数据库
Yz98762 小时前
hive的存储格式
大数据·数据库·数据仓库·hive·hadoop·数据库开发
苏-言2 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
Ljw...3 小时前
索引(MySQL)
数据库·mysql·索引