美文网首页
mongo 聚合操作

mongo 聚合操作

作者: 阿兵云原生 | 来源:发表于2022-10-03 21:45 被阅读0次

    清空集合中的文档

    db.users.drop()
    

    数据准备

    准备 users 表数据

    在 users 里面准备一组数据,包含 item ,qty,status,tags 和 size 字段,其中 size 是内嵌文档,size 里面又包含了 h,w,uom 字段

    db.users.insertMany([
       { item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status: "A" },
       { item: "journal", qty: 25, tags: ["blank", "red", "small"], size: { h: 14, w: 21, uom: "cm" }, status: "A" },
       { item: "notebook", qty: 50, tags: ["gray", "yellow", "green"], size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
       { item: "paper", qty: 100, tags: ["gray", "yellow", "green"], size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
       { item: "planner", qty: 75, tags: ["gray", "yellow", "green"], size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
       { item: "postcard", qty: 45, tags: ["gray", "yellow", "green"], size: { h: 10, w: 15.25, uom: "cm" }, status: "A" },
       { item: "sketchbook", qty: 80, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
       { item: "sketch pad", qty: 95, size: { h: 22.85, w: 30.5, uom: "cm" }, status: "A" },
       { item: "mat", qty: 85, tags: ["gray", "yellow", "green"], size: { h: 27.9, w: 35.5, uom: "cm" }, status: "A" },
       { item: "mousepad", qty: 25, tags: ["gel", "blue", "big"], size: { h: 19, w: 22.85, uom: "cm" }, status: "A" },
       { item: "mobile", qty: 250, tags: ["red", "big"], status: "A" },
       { item: "map", qty: 250, length: [129, 500, 1000], status: "A" },
       { item: "apple", qty: 250, length: [29, 50, 90], status: "A" },
       { item: "banana", qty: 150, status: "A" },
       { item: "orange", qty: 90, size: { h: 19, w: 22.85, uom: "cm" }, status: "A" },
       { "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
       { "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
       { "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
       { "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
       { "_id" : 5, "sku": null, description: "Incomplete" },
       { "_id" : 6 }
    ]);
    

    这里可以看到准备的数据,最后 6 个文档,我们自己指定了 _id 字段的值

    结果如下:

    [图片上传失败...(image-aa03e0-1664804663205)]

    我们插入的文档中,没有自己指定 _id 字段,则 mongodb 会为我们生成这个主键,若我们自己指定了这个字段,那么就会按照我们自定义的方式来

    准备 sales 字段

    在文档中加入日期字段,整型字段,小数字段,分别使用 mongodb 的函数

    • ISODate
    • NumberInt
    • NumberDecimal
    db.sales.insertMany([
      { "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
      { "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
      { "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
      { "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" :  NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
      { "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
      { "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
      { "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
      { "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
    ])
    

    orders 表数据准备

    db.orders.insert(
    [
      {
        "_id": 1,
        "item": "almonds",
        "price": 12,
        "quantity": 2
      },
      {
        "_id": 2,
        "item": "pecans",
        "price": 20,
        "quantity": 1
      },
      {
        "_id": 3
      }
    ]
    )
    

    数据聚合操作

    计算集合的文档数

    通过 _id 字段分组,此处分组条件是 _id 字段为空,表示筛选所有的文档, $sum:1 表示有 1 个文档就加 1,最后以 salesCount 字段展示出来

    > db.sales.aggregate( [{$group: {_id: null, salesCount: { $sum: 1 } }}] )
    { "_id" : null, "salesCount" : 8 }
    

    其余两个表格做法一致

    > db.users.aggregate( 
    [
      {
        "$group": {
          "_id": null,
          "usersCount": {
            "$sum": 1
          }
        }
      }
    ]
    )
    { "_id" : null, "usersCount" : 21 }
    > db.orders.aggregate( 
    [
      {
        "$group": {
          "_id": null,
          "ordersCount": {
            "$sum": 1
          }
        }
      }
    ]
    )
    { "_id" : null, "ordersCount" : 3 }
    >
    

    通过以上方式,我们就能很快的得出每一个集合的文档个数,当然我们还可以加上别的筛选条件来聚合数据

    例如我们可以这样,先筛选出 price 字段大于 5 的文档数,才统计文档的个数,处理思路如下:

    分成 2 步进行

    • 先找到 price 大于 5 的文档列表,作为下一个步骤的管道输入
    • 拿到上述输入后,计算文档个数
    > db.sales.aggregate( [{$match:{price:{$gt:NumberDecimal("5")}}}])
    { "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : 2, "date" : ISODate("2014-03-01T08:00:00Z") }
    { "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : 1, "date" : ISODate("2014-03-01T09:00:00Z") }
    { "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : 10, "date" : ISODate("2014-04-04T21:23:13.331Z") }
    { "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity" : 5, "date" : ISODate("2015-06-04T05:08:13Z") }
    { "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity" : 10, "date" : ISODate("2015-09-10T08:43:00Z") }
    { "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : 5, "date" : ISODate("2016-02-06T20:20:13Z") }
    

    聚合看看数量

    > db.sales.aggregate( 
    [
      {
        $match:{
          price:{
            $gt:NumberDecimal("5")
          }
        }
      },
      {
        $group:{
          _id:null,
          count:{
            $sum:1
          }
        }
      }
    ]
    )
    { "_id" : null, "count" : 6 }
    

    果然是 6 个文档,没错

    计算 sales 表格 每一个条目的总价,筛选出 大于 100 的

    思路如下:

    分成 2 步进行

    • 先计算出每一个条目的数量与价格的乘积结果,放到一张临时表中
    • 从临时表中筛选出结果大于 100 的条目

    上述说的临时表,其实我们此处用到的是聚合管道,例如这样

    [图片上传失败...(image-5769e3-1664804663205)]

    db.sales.aggregate(
      [
        // First Stage
        {
          $group :
            {
              _id : "$item",
              totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
            }
         },
         // Second Stage
         {
           $match: { "totalSaleAmount": { $gte: 100 } }
         }
       ]
     )
    

    看到这里,不要以为咱们只能分成 2 步骤来实现,我们上一篇文章写到过,这些阶段的关键字都是可以重复使用的,只是某几个特殊的关键字不能重复使用

    [图片上传失败...(image-b435e0-1664804663205)]

    例如下面这个例子,我们就可以 $match 多次,最后计算出一个结果,实际应用中,我们可以根据我们的需求来进行分批次处理,怎么方便怎么来

    > db.sales.aggregate( 
    [
      {
        $match:{
          price:{
            $gt:NumberDecimal("5")
          }
        }
      },
      {
        $match:{
          price:{
            $gt:NumberDecimal("10")
          }
        }
      },
      {
        $group:{
          _id:null,
          count:{
            $sum:1
          }
        }
      }
    ]
    )
    { "_id" : null, "count" : 1 }
    

    稍微复杂点的例子

    操作 sales 表

    • 筛选出日期在 2014-01-01 到 2015-01-01 之间的数据
    • 分组,
      • 将_id 赋值为 字符串的日期格式,
      • 将 totalSaleAmount 赋值为 原表 price 和 quantity 的乘积 再将同样日期的乘积结果相加
      • 将 averageQuantity 赋值为 quantity 的平均数
      • count 计算文档个数
    • 排序, -1 是倒序, 1 是正序
    • project 控制显示的字段
    db.sales.aggregate([
      {
        $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } }
      },
      {
        $group : {
           _id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
           totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
           averageQuantity: { $avg: "$quantity" },
           count: { $sum: 1 }
        }
      },
      { $sort : { totalSaleAmount: -1 } },
      // 控制所需要显示的字段名
     { $project : { _id : 1 , totalSaleAmount : 1 } }
     ])
    

    [图片上传失败...(image-65d53d-1664804663205)]

    打开 project 的注释,咱们就只控制显示 _id 和 totlSaleAmount 字段,结果如下

    { "_id" : "2014-04-04", "totalSaleAmount" : NumberDecimal("200") }
    { "_id" : "2014-03-15", "totalSaleAmount" : NumberDecimal("50") }
    { "_id" : "2014-03-01", "totalSaleAmount" : NumberDecimal("40") }
    

    多表操作

    最后来演示一个多表操作的例子

    咱们查询 users 和 orders 表,分别关联 orders 的 item 和 users 的 sku 字段,结果放到 users_docs 中

    db.orders.aggregate([
       {
         $lookup:
           {
             from: "users",
             localField: "item",
             foreignField: "sku",
             as: "users_docs"
           }
      }
    ])
    

    [图片上传失败...(image-869dd-1664804663205)]

    分页

    我们先来看看如何将 users 表中的 tags 数组元素都变成对象

    查询 users 表中数据,可以看出 tags 还是一个数组

    db.users.find().pretty()
    

    [图片上传失败...(image-698dd2-1664804663205)]

    使用 unwind 来将元素做成文档,可以看出 tags 不在是数组,而是字符串了

    > db.users.aggregate( [ { $unwind : "$tags" } ] )
    { "_id" : ObjectId("615d049f3ea73badd681950e"), "item" : "journal", "qty" : 25, "tags" : "blank", "size" : { "h" : 14, "w" : 21, "uom" : "cm" }, "status" : "A" }
    { "_id" : ObjectId("615d049f3ea73badd681950e"), "item" : "journal", "qty" : 25, "tags" : "red", "size" : { "h" : 14, "w" : 21, "uom" : "cm" }, "status" : "A" }
    { "_id" : ObjectId("615d049f3ea73badd681950e"), "item" : "journal", "qty" : 25, "tags" : "small", "size" : { "h" : 14, "w" : 21, "uom" : "cm" }, "status" : "A" }
    ...
    

    [图片上传失败...(image-dea57d-1664804663205)]

    开始我们的实践

    • 我们将 users 表中的 tags 数组中的元素,都做成一个对象
    • 分组,按照 tags 来进行分组,_id 赋值为 tags 字段,averageQty 赋值为 qty 字段的根据 tags 的平均数
    • 在倒序排列
    • 显示的时候,跳过前面 2 个 ,显示后面 2 个
    db.users.aggregate( [
       {
         $unwind: { path: "$tags", preserveNullAndEmptyArrays: true }
       },
       {
         $group:
           {
             _id: "$tags",
             averageQty: { $avg: "$qty" }
           }
       },
       { $sort: { "averageQty": -1 } },
       //{ $skip: 2},
       //{ $limit: 2}
    ] )
    
    { "_id" : "red", "averageQty" : 137.5 }
    { "_id" : "green", "averageQty" : 71 }
    

    不加分页的话,我们可以看到结果是这样子的

    [图片上传失败...(image-8fe0e8-1664804663205)]

    欢迎点赞,关注,收藏

    朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

    [图片上传失败...(image-93581d-1664804663205)]

    好了,本次就到这里

    技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

    我是阿兵云原生,欢迎点赞关注收藏,下次见~

    相关文章

      网友评论

          本文标题:mongo 聚合操作

          本文链接:https://www.haomeiwen.com/subject/rdgknrtx.html