MongoDB-聚合查询使用先容

[复制链接]
发表于 2026-1-24 20:24:51 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
媒介

MongoDB聚合使用相称于关系型数据库SQL语句的"group by"、“order by"这种语句,而我们MongoDB的聚合使用也是使用到group这个聚合函数,但是在先容聚合函数之前,必要先先容一个,查询下令"db.collection.aggregate()”,之前我们使用的查询方法都是”db.collection.find()“;而当MongoDB必要举行聚合使用时,比方取均匀值、统计数据,就必要用到"db.collection.aggregate()"方法了。
db.collection.aggregate()



  • 使用 db.collection.aggregate() 的方法运行聚合管道的使用,并不会真的修改文档,除非管道中包罗了$merge和$out函数,这两个函数都是将结果写入到指定的聚集,out是可以指定输出的数据库是哪个。
  • 理论上来说db.collection.aggregate()的服从实在是不如find的,由于db.collection.aggregate()肯定是要做聚合使用才使用,否则就用find了。固然db.collection.aggregate()不加任何条件,也可以像find一样查询聚集中的全部数据。
  • aggregate的语法格式:db.collection.aggregate(pipeline, options),此中pipeline‌ 为一个数组,重要用于使用一个聚合函数,做到查询过滤、分组排序、投影设置输出字段等。options‌ 呢是一个可选的文档参数,重要是用于通报聚合下令的其他选项、好比上方提到的 $out 或 $merge 。乃至另有explain实行操持等。
  • db.collection.aggregate()的限定,MongoDB 5.0 版本后将单个管道中允许的聚合管道阶段限定为 1000 个,每个单独的管道阶段的 RAM 限定为 100 MB。 默认情况下,如果某个阶段高出此限定,MongoDB 会产生错误。 对于某些管道阶段,您可以使用allowDiskUse选项启用聚合管道阶段以将数据写入暂时文件,从而允许管道处置惩罚占用更多空间,$search 聚合阶段不限于 100 MB 的 RAM,由于它在单独的历程中运行。
db.collection.aggregate()示例一

  1. ###插入一批测试数据:name为匹萨的种类、size是披萨的尺寸、price是披萨的单价、quantity是售卖的订单数量、date为时间。
  2. db.orders.insertMany( [
  3.    { _id: 0, name: "Pepperoni", size: "small", price: 19,
  4.      quantity: 10, date: ISODate( "2021-03-13T08:14:30Z" ) },
  5.    { _id: 1, name: "Pepperoni", size: "medium", price: 20,
  6.      quantity: 20, date : ISODate( "2021-03-13T09:13:24Z" ) },
  7.    { _id: 2, name: "Pepperoni", size: "large", price: 21,
  8.      quantity: 30, date : ISODate( "2021-03-17T09:22:12Z" ) },
  9.    { _id: 3, name: "Cheese", size: "small", price: 12,
  10.      quantity: 15, date : ISODate( "2021-03-13T11:21:39.736Z" ) },
  11.    { _id: 4, name: "Cheese", size: "medium", price: 13,
  12.      quantity:50, date : ISODate( "2022-01-12T21:23:13.331Z" ) },
  13.    { _id: 5, name: "Cheese", size: "large", price: 14,
  14.      quantity: 10, date : ISODate( "2022-01-12T05:08:13Z" ) },
  15.    { _id: 6, name: "Vegan", size: "small", price: 17,
  16.      quantity: 10, date : ISODate( "2021-01-13T05:08:13Z" ) },
  17.    { _id: 7, name: "Vegan", size: "medium", price: 18,
  18.      quantity: 10, date : ISODate( "2021-01-13T05:10:13Z" ) }
  19. ] )
  20. ###查询数据,使用db.orders.aggregate()也可以像find命令一样直接查询数据。
  21. Enterprise test [direct: primary] mongodb_test> db.orders.aggregate()
  22. [
  23.   {
  24.     _id: 0,
  25.     name: 'Pepperoni',
  26.     size: 'small',
  27.     price: 19,
  28.     quantity: 10,
  29.     date: ISODate('2021-03-13T08:14:30.000Z')
  30.   },
  31.   {
  32.     _id: 1,
  33.     name: 'Pepperoni',
  34.     size: 'medium',
  35.     price: 20,
  36.     quantity: 20,
  37.     date: ISODate('2021-03-13T09:13:24.000Z')
  38.   },
  39.   {
  40.     _id: 2,
  41.     name: 'Pepperoni',
  42.     size: 'large',
  43.     price: 21,
  44.     quantity: 30,
  45.     date: ISODate('2021-03-17T09:22:12.000Z')
  46.   },
  47. ...
  48. ###使用聚合操作过滤size字段等于medium的数据,按照name进行分组排序,计算quantity列数据的总和,其中过滤使用到match函数,分组排序使用到group函数、计算总和用到sum函数。
  49. mongodb_test> db.orders.aggregate( [
  50. {
  51. $match: { size: "medium" }
  52. },
  53. {
  54.    $group: { _id: "$name", totalQuantity: { $sum: "$quantity" } }
  55. }
  56. ] )
  57. ...
  58. [
  59.   { _id: 'Cheese', totalQuantity: 50 },
  60.   { _id: 'Vegan', totalQuantity: 10 },
  61.   { _id: 'Pepperoni', totalQuantity: 20 }
  62. ]
  63. ###此时通过查询可以发现,我们的数据并没有被修改或变更。所有的操作都在内存中实现
  64. Enterprise test [direct: primary] mongodb_test> db.orders.aggregate()
  65. [
  66.   {
  67.     _id: 0,
  68.     name: 'Pepperoni',
  69.     size: 'small',
  70.     price: 19,
  71.     quantity: 10,
  72.     date: ISODate('2021-03-13T08:14:30.000Z')
  73.   },
  74.   ...
复制代码


  • 上方的聚合使用实行流程为:先通过match函数过滤了size为medium的数据,然后把数据通报下一阶段的管道,给了group函数,group函数拿到数据后,根据name罗列行分组排序此处的_id为表达式指定组键简朴说_id反面跟哪个列就是按照哪个列排序,并通过sum函数对名字类似的quantity列的数据举行相加,输出结果为totalQuantity。
db.collection.aggregate()示例二

  1. ###过滤一个指定日期范围内,每天的披萨的订单总额是多少,并且所有种类披萨加一起,平均出了多少单。根据订单总额从大到小排序
  2. Enterprise test [direct: primary] mongodb_test> db.orders.aggregate( [
  3. ...
  4. ...    // Stage 1: Filter pizza order documents by date range
  5. ...    {
  6. ...       $match:
  7. ...       {
  8. ...          "date": { $gte: new ISODate( "2020-01-30" ), $lt: new ISODate( "2022-01-30" ) }
  9. ...       }
  10. ...    },
  11. ...
  12. ...    // Stage 2: Group remaining documents by date and calculate results
  13. ...    {
  14. ...       $group:
  15. ...       {
  16. ...          _id: { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
  17. ...          totalOrderValue: { $sum: { $multiply: [ "$price", "$quantity" ] } },
  18. ...          averageOrderQuantity: { $avg: "$quantity" }
  19. ...       }
  20. ...    },
  21. ...
  22. ...    // Stage 3: Sort documents by totalOrderValue in descending order
  23. ...    {
  24. ...       $sort: { totalOrderValue: -1 }
  25. ...    }
  26. ...
  27. ...  ] )
  28. [
  29.   { _id: '2022-01-12', totalOrderValue: 790, averageOrderQuantity: 30 },
  30.   { _id: '2021-03-13', totalOrderValue: 770, averageOrderQuantity: 15 },
  31.   { _id: '2021-03-17', totalOrderValue: 630, averageOrderQuantity: 30 },
  32.   { _id: '2021-01-13', totalOrderValue: 350, averageOrderQuantity: 10 }
复制代码


  • match阶段根据时间范围为大于即是2020年1月30日,小于2022年1月30日过滤数据。
  • 根据match过滤的数据通报给group,group通过dateToString函数按照年代日举行分区排序,然后通过multiply函数让price乘以quantity,再根据分组举行sum,让年代日类似的分组举行相加。末了输出为totalOrderValue字段。再通过avg函数,对类似年代日的数据,取quantity字段的均匀值(将quantity列数据相加除以类似分组有多少条数据),末了输出为averageOrderQuantity字段。
  • 末了的sort函数数根据totalOrderValue字段,按照-1排序,-1表现降序。从大到小分列。
复杂的聚合使用



  • 根据上方的先容,根本上对聚合使用有了肯定的认识,下面将难度提升,看一下复杂的聚合使用又是怎么实现的。
通过mongoimport导入测试数据



  • 为了反面的使用,我们先导入一批数据,这个测试数据泉源自MongoDB官网。
   测试数据地点:https://media.mongodb.org/zips.json
  1. ### 安装mongodb tools工具,里面包含了mongoimport
  2. root # wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel70-x86_64-100.10.0.rpm
  3. root # yum localinstall -y mongodb-database-tools-rhel70-x86_64-100.10.0.rpm
  4. ### 下载测试文件到服务器
  5. root # wget https://media.mongodb.org/zips.json
  6. ### 使用mongodb的mongoimport 导入数据到MongoDB里
  7. root # mongoimport --username admin --password admin --host 127.0.0.1 --port 27017 --authenticationDatabase=admin -d mongodb_test --type=json --file=./zips.json
复制代码


  • authenticationDatabase指定认证库
  • -d指定导入到的数据库
  • type=json表现这是个json的文档
  • file表现指定必要导入的文件
复杂的聚合使用示例

1.根据zips聚集中的state字段(州)和pop(都会地域生齿)列的相加举行分组排序,再盘算小于100万生齿的州有哪些。
  1. db.zips.aggregate( [
  2.    { $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
  3.    { $match: { totalPop: { $lt: 1000000 } } }
  4. ] )
  5. [
  6.   { _id: 'ND', totalPop: 638272 },
  7.   { _id: 'AK', totalPop: 544698 },
  8.   { _id: 'MT', totalPop: 798948 },
  9.   { _id: 'VT', totalPop: 562758 },
  10.   { _id: 'SD', totalPop: 695397 },
  11.   { _id: 'WY', totalPop: 453528 },
  12.   { _id: 'DC', totalPop: 606900 },
  13.   { _id: 'DE', totalPop: 666168 }
  14. ]
复制代码


  • group阶段,按照两个罗列行分组,一个州信息缩写state列,和totalPop列,totalPop列为全部州生齿的总和。
  • match阶段得时间,被group通报了两列数据,一个是_id列为state得州信息,一个是totalPop总生齿信息,随后通过对totalPop罗列行过滤条件匹配,返回数据。
   这个测试数据中,差别的州大概拥有类似的都会、类似的都会地域差别,生齿差别。
  2.盘算每个州state、每个都会city,每个地域的,均匀生齿有多少。必要先按照州、都会、和地域举行分组排序,然后将生齿举行相加。随后通太过组后的数据,二次根据州都会分组排序,取总生齿的均匀值。
  1. ###先计算每个州每个城市的总人口,数据量很大。
  2. db.zips.aggregate( [
  3.    { $group: { _id: { state: "$state", city:"$city",loc:"$loc" },pop: { $sum: "$pop" } } },
  4. ] )
  5. ###通过group聚合操作,再次对state州和城市进行分组排序。
  6. db.zips.aggregate( [
  7.    { $group: { _id: { state: "$state", city:"$city" ,loc:"$loc"}, pop: { $sum: "$pop" } } },
  8.    { $group: { _id:{state:"$_id.state",city:"$_id.city"}}}
  9. ] )
  10. ###现在可以取平均值了。
  11. db.zips.aggregate( [
  12.    { $group: { _id: { state: "$state", city:"$city",loc:"$loc" }, pop: { $sum: "$pop" } } },
  13.    { $group: { _id:{state:"$_id.state",city:"$_id.city"}, avgcityloc:{$avg:"$pop"}}}
  14. ] )
复制代码


  • 先通过聚合函数group,对state、city、loc三罗列行分组排序,随后使用sum函数举行相加。这时间通报给下个管道时就是有四列信息,分别是state、city、loc、以及相加后的pop。
  • 二次group时再对排序过再次做聚合使用,通过$_id.state和$_id.city字段的方式对,通报过来的数据举行二次聚合使用,聚合完成绩可以直接通过avg函数对pop字段取均匀值了。
   小知识:在mongoshell中,当输出结果集行数过多,默认是表现不完的,必要设置参数指定必要表现的行数,DBQuery.shellBatchSize = 100000,这里表现设置输出结果集表现1万行。
  3.按照state、city、地域,返回最大生齿的都会和最小的都会是哪两个。
  1. ###先根据state和city进行排序,然后对人口进行相加得到每个state州的每个city城市有多少人口。
  2. db.zips.aggregate( [
  3.    { $group:
  4.       {
  5.         _id: { state: "$state", city: "$city" },
  6.         pop: { $sum: "$pop" }
  7.       }
  8.    }
  9. ] )
  10. ###使用前面提到的sort函数,对人口进行从小到达的一个排序,这样我们就得到了一个有序的数据。
  11. db.zips.aggregate( [
  12.    { $group:
  13.       {
  14.         _id: { state: "$state", city: "$city" },
  15.         pop: { $sum: "$pop" }
  16.       }
  17.    },
  18.    { $sort: { pop: 1 } }
  19. ] )
  20. ###此时就要开始二次聚合操作,处理数据了。根据州state,进行分组排序,但是不同的是我们在排序的同时需要获取最大人口的城市和最小人口的城市。因为我们是按照州排序的,就可以通过两个参数,捞到每个州最大人口的城市和最小的城市了,一个是函数$last一个是first。分别是获取文档中最后一条数据和第一条数据。而刚才我们通过sort排序后最大的数据会在最后一行,最小的会在第一行。
  21. db.zips.aggregate( [
  22.    { $group:
  23.       {
  24.         _id: { state: "$state", city: "$city" },
  25.         pop: { $sum: "$pop" }
  26.       }
  27.    },
  28.    { $sort: { pop: 1 } },
  29.    {$group:
  30.       {
  31.         _id : "$_id.state",
  32.         biggestCity:  { $last: "$_id.city" },
  33.         biggestPop:   { $last: "$pop" },
  34.         smallestCity: { $first: "$_id.city" },
  35.         smallestPop:  { $first: "$pop" }
  36.       }}
  37. ] )
  38. ###此时其实已经满足了我们需求,但是为了更好看可以使用 $project函数,它可以修改我们的输出结果。重定向字段。
  39. db.zipcodes.aggregate( [
  40.    { $group:
  41.       {
  42.         _id: { state: "$state", city: "$city" },
  43.         pop: { $sum: "$pop" }
  44.       }
  45.    },
  46.    { $sort: { pop: 1 } },
  47.    { $group:
  48.       {
  49.         _id : "$_id.state",
  50.         biggestCity:  { $last: "$_id.city" },
  51.         biggestPop:   { $last: "$pop" },
  52.         smallestCity: { $first: "$_id.city" },
  53.         smallestPop:  { $first: "$pop" }
  54.       }
  55.    },
  56.   { $project:
  57.     { _id: 0,
  58.       state: "$_id",
  59.       biggestCity:  { name: "$biggestCity",  pop: "$biggestPop" },
  60.       smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
  61.     }
  62.   }
  63. ] )
复制代码


  • 通过第一个group对state和city举行分组,然后通过sum函数获取到了city的总生齿
  • 再通过sort举行升序排序。将最大的值放在最下面,将最小的值放在最反面。
  • 第二个group通过对_id.state再次分组排序,然后根据州的分组,通过last和first函数,取出city和pop列取出每个州第一行数据,和末了一行数据。表现最大值和最小值。
  • 通过project函数输出重定向为一个自界说文档,_id:0为克制这个字段的输出,再通过新建state,输出_id列的值。创建文档biggestCity和smallestCity,内里的name和pop,分别调用biggestCity和biggestPop,以及smallestCity和smallestPop。返回输出结果。
聚合使用的偏好设置-project函数



  • project函数重要用于,将上层管道通报来的全部数据,也可以是将数据通报给下个管道,数据泉源可以是输入已有的文档内容,也可以举行本身盘算得出新的字段。
  • project的常勤奋能重要有:克制字段(包罗嵌入式文档)、清除字段(包罗嵌入式文档)、添加新字段或重置现有字段,以及上方提到的,自界说文档输出。
  • 在project中不支持数组索引
1.插入一批测试数据
  1. db.members.insertMany( [
  2.    {
  3.       _id: "jane",
  4.       joined: ISODate("2011-03-02"),
  5.       likes: ["golf", "racquetball"]
  6.    },
  7.    {
  8.       _id: "joe",
  9.       joined: ISODate("2012-07-02"),
  10.       likes: ["tennis", "golf", "swimming"]
  11.    },
  12.    {
  13.       _id: "ruth",
  14.       joined: ISODate("2012-01-14"),
  15.       likes: ["golf", "racquetball"]
  16.    },
  17.    {
  18.       _id: "harold",
  19.       joined: ISODate("2012-01-21"),
  20.       likes: ["handball", "golf", "racquetball"]
  21.    },
  22.    {
  23.       _id: "kate",
  24.       joined: ISODate("2012-01-14"),
  25.       likes: ["swimming", "tennis"]
  26.    }
  27. ] )
复制代码
2.通过project函数克制_id字段的输出或只输出_id字段。
  1. ###抑制_id字段
  2. db.members.aggregate(
  3.    [
  4.       { $project: { _id: 0 } }
  5.    ]
  6. )
  7. ###只输出id字段
  8. db.members.aggregate(
  9.    [
  10.       { $project: { _id: 1 } }
  11.    ]
  12. )
复制代码
2.对name字段内容举行全部大写处置惩罚后再排序输出
  1. db.members.aggregate(
  2.   [
  3.     { $project: { name: { $toUpper: "$_id" }, _id: 0 } },
  4.     { $sort: { name: 1 } }
  5.   ]
  6. )
复制代码


  • project阶段,将_id列通过toUpper函数处置惩罚玉成部大写,随后再重界说为name字段,然后将_id字段克制,由于不克制,_id字段默认是会输出结果的。随后通报给下一管道,sort。
  • sort拿到数据后,对新建的name罗列行升序分列。返回数据。
2.按照月份重界说字段,升序输出。
  1. db.members.aggregate( [
  2.     {
  3.       $project: {
  4.          month_joined: { $month: "$joined" },
  5.          name: "$_id",
  6.          _id: 0
  7.        }
  8.     },
  9.     { $sort: { month_joined: 1 } }
  10. ] )
复制代码


  • project阶段,通过month函数对日期字段joined举行处置惩罚,month按照时间返回月份的整数(1、2、3月如许),也就是只表现月份。随后重界说字段为month_joined,再输出新界说name列,值为_id列。随后克制_id列输出。
  • sort阶段通过新界说的字段month_joined举行升序分列。
3.按照月份排序,并统计类似的月份有多少数据,随后再按照数量举行升序排序
  1. db.members.aggregate( [
  2.    { $project: { month_joined: { $month: "$joined" } } } ,
  3.    { $group: { _id: { month_joined: "$month_joined" } , count: { $sum: 1 } } },
  4.    { $sort: { count: 1 } }
  5. ] )
复制代码


  • project阶段通过month函数对joined罗列行只输出月份处置惩罚,重界说为month_joined字段。通报给下一个管道group。
  • group阶段拿到month_joined列这行数据,举行分组排序,再通过$sum: 1的方式,统计类似月份的数据行有多少。然后通报给下个管道的函数sort
  • sort拿到数据后,按照count列,举行升序分列。
聚合使用的偏好设置-unwind函数



  • unwind函数重要是分析输入文档中的数组字段,然后再为每个数组中的元素输出成文档。每个输出的文档都是和输入文档一样的字段,只是在数组字段时被更换了,从下方可以看到,就是将一行数据酿成了多行数据。
  1. Enterprise test [direct: primary] mongodb_test> db.members.aggregate(
  2. ...   [
  3. ...     { $unwind: "$likes" }
  4. ...   ]
  5. ... )
  6. [
  7.   {
  8.     _id: 'jane',
  9.     joined: ISODate('2011-03-02T00:00:00.000Z'),
  10.     likes: 'golf'
  11.   },
  12.   {
  13.     _id: 'jane',
  14.     joined: ISODate('2011-03-02T00:00:00.000Z'),
  15.     likes: 'racquetball'
  16.   },
  17.   {
  18.     _id: 'joe',
  19.     joined: ISODate('2012-07-02T00:00:00.000Z'),
  20.     likes: 'tennis'
  21.   },
  22.   {
  23.     _id: 'joe',
  24.     joined: ISODate('2012-07-02T00:00:00.000Z'),
  25.     likes: 'golf'
  26.   },
  27.   {
  28.     _id: 'joe',
  29.     joined: ISODate('2012-07-02T00:00:00.000Z'),
  30.     likes: 'swimming'
  31.   },
  32.   {
  33.     _id: 'ruth',
  34.     joined: ISODate('2012-01-14T00:00:00.000Z'),
  35.     likes: 'golf'
  36.   },
  37.   {
  38.     _id: 'ruth',
  39.     joined: ISODate('2012-01-14T00:00:00.000Z'),
  40.     likes: 'racquetball'
  41.   },
  42.   {
  43.     _id: 'harold',
  44.     joined: ISODate('2012-01-21T00:00:00.000Z'),
  45.     likes: 'handball'
  46.   },
  47.   {
  48.     _id: 'harold',
  49.     joined: ISODate('2012-01-21T00:00:00.000Z'),
  50.     likes: 'golf'
  51.   },
  52.   {
  53.     _id: 'harold',
  54.     joined: ISODate('2012-01-21T00:00:00.000Z'),
  55.     likes: 'racquetball'
  56.   },
  57.   {
  58.     _id: 'kate',
  59.     joined: ISODate('2012-01-14T00:00:00.000Z'),
  60.     likes: 'swimming'
  61.   },
  62.   {
  63.     _id: 'kate',
  64.     joined: ISODate('2012-01-14T00:00:00.000Z'),
  65.     likes: 'tennis'
  66.   }
  67. ]
复制代码


  • 输出likes字段这个数组中,出现最多的元素是哪个,而且举行降序分列,而且只表现前五行。
  1. db.members.aggregate(
  2.   [
  3.     { $unwind: "$likes" },
  4.     { $group: { _id: "$likes" , count: { $sum: 1 } } },
  5.     { $sort: { count: -1 } },
  6.     { $limit: 5 }
  7.   ]
  8. )
  9. ...
  10. [
  11.   { _id: 'golf', count: 4 },
  12.   { _id: 'racquetball', count: 3 },
  13.   { _id: 'swimming', count: 2 },
  14.   { _id: 'tennis', count: 2 },
  15.   { _id: 'handball', count: 1 }
  16. ]
复制代码


  • 由于我们必要对数组举行处置惩罚,以是第一个管道,先将数据拆解成多个文档。使用unwind函数,然后再通报给下个管道group
  • group阶段,通过对likes字段排序,再通过sum函数,对类似likes字段的数据行举行计数,通报给下个管道sort
  • sort通过sum统计的列,count举行降序分列,通报给下个管道limit
  • limit函数表现表现几行数据,这里表现5行,和MySQL的limit类似。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表