07、MongoDB实战:MongoDB聚合aggregate

聚合框架aggregate

聚合框架用于查询操作,使用聚合框架agregate可以通过多个操作符对文档进行处理,将前一个操作符处理后的结果传给下一个操作符,最后一个操作符处理的结果就是聚合的最后的结果。

> db.book.insertMany([{
   
     "_id": 1, "author": "monday", "book": "《Java》", "like": 10}, {
   
     "_id": 2, "author": "monday", "book": "《Java Core》", "like": 20}, {
   
     "_id": 3, "author": "mengday", "book": "《Spring Boot》", "like": 15}])

> db.book.find()
{
   
      "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10 }
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20 }
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15 }

$match 筛选

筛选条件,相当于SQL中的where部分,过滤掉不满足条件的文档,可以使用常规的查询操作符,如 $gt、$lt、$in等。

> db.book.aggregate({
   
     "$match": {
   
     "like": {
   
     "$gt": 10}}})
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20 }
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15 }

$project 投射

投射相当于SQL中的 select 投射 from 之间的部分,用于指定要查询的字段,或者对字段进行一些处理。
投射常用的3个功能:

  • 用于包含、排除字段: 设置要查询或者要过滤掉的字段,0: 要过滤掉的字段,不显示,1:需要查询的字段。
  • 对字段重命名。
  • 在投射中使用一些表达式对字段值进行处理:数学表达式、日期表达式、字符串表达式、逻辑表达式(比较表达式、布尔表达式、控制语句)。
// 查询自定的字段
> db.book.aggregate({
   
     "$project": {
   
     "_id": 0, "book": 1, "like": 1}})
{
   
      "book" : "《Java》", "like" : 10 }
{
   
      "book" : "《Java Core》", "like" : 20 }
{
   
      "book" : "《Spring Boot》", "like" : 15 }

// 对字段起别名,相当于SQL中的as的作用
> db.book.aggregate({
   
     "$project": {
   
     "_id": 0, "book name": "$book", "like": 1}})
{
   
      "like" : 10, "book name" : "《Java》" }
{
   
      "like" : 20, "book name" : "《Java Core》" }
{
   
      "like" : 15, "book name" : "《Spring Boot》" }

// 如果对 _id进行起别名会将别名作为一个新的字段加入到文档中,而_id保持不变
> db.book.aggregate({
   
     "$project": {
   
     "id": "$_id", "book name": "$book", "like": 1}})
{
   
      "_id" : 1, "like" : 10, "id" : 1, "book name" : "《Java》" }
{
   
      "_id" : 2, "like" : 20, "id" : 2, "book name" : "《Java Core》" }
{
   
      "_id" : 3, "like" : 15, "id" : 3, "book name" : "《Spring Boot》" }

// 使用算术表达式 $add、$subtract、$multiply、$divide、$mod 处理数字类型的字段
> db.book.aggregate({
   
     "$project": {
   
     "like": {
   
     "$add": ["$like", "$like", 1]}}})
{
   
      "_id" : 1, "like" : 21 }
{
   
      "_id" : 2, "like" : 41 }
{
   
      "_id" : 3, "like" : 31 }

// $substrCP: [exp, startOffset, numToReturn] : 字符串截取操作
> db.book.aggregate({
   
     "$project": {
   
     "newValue": {
   
     "$substrCP": ["$book", 1, 4]}}})
{
   
      "_id" : 1, "newValue" : "Java" }
{
   
      "_id" : 2, "newValue" : "Java" }
{
   
      "_id" : 3, "newValue" : "Spri" }

// $concat:[exp1, exp2, ..., expN]: 字符串操作:将数组中的多个元素拼接在一起
> db.book.aggregate({
   
     "$project": {
   
     "newValue": {
   
     "$concat": ["$book", "(", "$author", ")"]}}})
{
   
      "_id" : 1, "newValue" : "《Java》(monday)" }
{
   
      "_id" : 2, "newValue" : "《Java Core》(monday)" }
{
   
      "_id" : 3, "newValue" : "《Spring Boot》(mengday)" }

// $toUpper: 字符串操作,转大写
// $toLower : exp, 字符串转小写
> db.book.aggregate({
   
     "$project": {
   
     "newValue": {
   
     "$toUpper": "$book"}}})
{
   
      "_id" : 1, "newValue" : "《JAVA》" }
{
   
      "_id" : 2, "newValue" : "《JAVA CORE》" }
{
   
      "_id" : 3, "newValue" : "《SPRING BOOT》" }

// 日期表达式:用于获取日期中的任意一部分,年月日时分秒 星期等
// $year、$month、$dayOfMonth、$dayOfWeek、$dayOfYear、$hour、$minute、$second
> db.book.find()
{
   
      "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
> db.book.aggregate({
   
     "$project": {
   
     "year": {
   
     "$year": "$publishDate"}}})
{
   
      "_id" : 1, "year" : 2021 }
{
   
      "_id" : 2, "year" : 2021 }
{
   
      "_id" : 3, "year" : 2021 }

// $cmp: [exp1, exp2]: 用于比较两个非字符串类型的值,exp1 == exp2 返回 0, 小于返回一个负数,大于返回一个正数
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$cmp": ["$like", 15]}}})
{
   
      "_id" : 1, "result" : -1 }
{
   
      "_id" : 2, "result" : 1 }
{
   
      "_id" : 3, "result" : 0 }

// $strcasecmp: 用于比较字符串, 不区分大小写,相等返回0
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$strcasecmp": ["$author", "Monday"]}}})
{
   
      "_id" : 1, "result" : 0 }
{
   
      "_id" : 2, "result" : 0 }
{
   
      "_id" : 3, "result" : -1 }
// $eq: 用于判断两个表达式是否相等,相等返回true,不相等返回false, 区分大小写
// $ne: 不相等
// $gt: 大于
// $gte: 大于等于
// $lt: 小于
// $lte: 小于等于
// 注意:{"$eq": ["$author", "monday"]} $eq键的值是个数组而不是一个对象
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$eq": ["$author", "monday"]}}})
{
   
      "_id" : 1, "result" : true }
{
   
      "_id" : 2, "result" : true }
{
   
      "_id" : 3, "result" : false }

// $and:[exp1, exp2, ..., expN] 用于连接多个条件,当所有条件为真的时候为true
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$and": [{
   
     "$eq": ["$author", "monday"]}, {
   
     "$gt": ["$_id", 1]}]}}})
{
   
      "_id" : 1, "result" : false }
{
   
      "_id" : 2, "result" : true }
{
   
      "_id" : 3, "result" : false }

// $or: [exp1, exp2, ..., expN]
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$or": [{
   
     "$eq": ["$author", "monday"]}, {
   
     "$eq": ["$_id", 1]}]}}})
{
   
      "_id" : 1, "result" : true }
{
   
      "_id" : 2, "result" : true }
{
   
      "_id" : 3, "result" : false }

// $not: exp 用于取反操作
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$not": {
   
     "$eq": ["$author", "monday"]}}}})
{
   
      "_id" : 1, "result" : false }
{
   
      "_id" : 2, "result" : false }
{
   
      "_id" : 3, "result" : true }

// $cond: [booleanExp, trueExp, falseExp]: 三位运算符
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$cond": [ {
   
     "$eq": ["$author", "monday"]}, "M", "F" ]}}})
{
   
      "_id" : 1, "result" : "M" }
{
   
      "_id" : 2, "result" : "M" }
{
   
      "_id" : 3, "result" : "F" }

// $ifNull: [expr, replacementExpr]: 如果字段不存在或者字段值为null会返回replacementExpr,否则返回原来的值,即字段为空时给一个默认值
> db.book.find()
{
   
      "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : 66 }
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : null }
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }

// IFNULL(bool, default_value)
> db.book.aggregate({
   
     "$project": {
   
     "result": {
   
     "$ifNull": ["$price", "0.00"]}}})
{
   
      "_id" : 1, "result" : 66 }
{
   
      "_id" : 2, "result" : "0.00" }
{
   
      "_id" : 3, "result" : "0.00" }

$group 分组

相当于SQL中的group by 部分。

// $group 表示分组
// _id: 用于指定要分组的字段
// count: 是聚合后的结果的字段别名,类似于SQL中的as后面的别名,可以任意定义,字段前面使用$表示应用某个字段而不是一个普通的字符串
// $sum: 对分组中的每个文档做什么类型的聚合,是求和还是求平均数等,1:表示对分组的每一条文档都加1进行统计,这就相当于SQL中的count(*)
// select _id, count(*) from book group by author
> db.book.aggregate({
   
     "$group": {
   
     "_id": "$author", "count": {
   
     "$sum": 1}}})
{
   
      "_id" : "monday", "count" : 2 }
{
   
      "_id" : "mengday", "count" : 1 }
// 对多个字段进行分组
// select _id, count(*) from book group by author, like
> db.book.aggregate({
   
     "$group": {
   
     "_id": {
   
     "author": "$author", "like": "$like"}, "count": {
   
     "$sum": 1}}})
{
   
      "_id" : {
   
      "author" : "monday", "like" : 10 }, "count" : 1 }
{
   
      "_id" : {
   
      "author" : "monday", "like" : 20 }, "count" : 1 }
{
   
      "_id" : {
   
      "author" : "mengday", "like" : 15 }, "count" : 1 }
// $avg: 求分组中某个字段的平均值
// $max: 求分组中某个字段的最大值
// $min: 求分组中某个字段最小的值
// $first: 求分组中的第一个值
// $last: 求分组中最后一个值
> db.book.aggregate( {
   
     "$group": {
   
     "_id": "$author", "avg": {
   
     "$avg": "$like"}}} )
{
   
      "_id" : "monday", "avg" : 15 }
{
   
      "_id" : "mengday", "avg" : 15 }
// $addToSet: 字段引用, 将分组后的每个文档指定的值放在set集合中,集合不重复,无序
> db.book.aggregate( {
   
     "$group": {
   
     "_id": "$author", "likes": {
   
     "$addToSet": "$like"}}} )
{
   
      "_id" : "monday", "likes" : [ 20, 10 ] }
{
   
      "_id" : "mengday", "likes" : [ 15 ] }

// $push: exp, 将分组后的每个文档指定的值放在数组中,允许重复,有序
> db.book.aggregate( {
   
     "$group": {
   
     "_id": "$author", "likes": {
   
     "$push": "$like"}}} )
{
   
      "_id" : "mengday", "likes" : [ 15 ] }
{
   
      "_id" : "monday", "likes" : [ 10, 20 ] }

$unwind

将数组的每一个元素都单独作为一条文档进行拆分。

> db.comments.insert({
   
     "_id": 1, "title": "java", "comment": ["good", "very good"]})
> db.comments.find()
{
   
      "_id" : 1, "title" : "java", "comment" : [ "good", "very good" ] }
> db.comments.aggregate( {
   
     "$unwind": "$comment"} )
{
   
      "_id" : 1, "title" : "java", "comment" : "good" }
{
   
      "_id" : 1, "title" : "java", "comment" : "very good" }

$sort

对文档进行排序,相当于SQL中的order by。

> db.book.aggregate( {
   
     "$sort": {
   
     "_id": -1}} )
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : null }
{
   
      "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : 66 }

$limit

限制返回的条数,相当于SQL中的limit count 语句。

> db.book.aggregate( {
   
     "$limit": 2} )
{
   
      "_id" : 1, "author" : "monday", "book" : "《Java》", "like" : 10, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : 66 }
{
   
      "_id" : 2, "author" : "monday", "book" : "《Java Core》", "like" : 20, "publishDate" : ISODate("2021-04-12T13:38:14.829Z"), "price" : null }

$skip

跳过前N条文档,和limit结合可用于分页。

> db.book.aggregate( {
   
     "$skip": 2}, {
   
     "$limit": 2} )
{
   
      "_id" : 3, "author" : "mengday", "book" : "《Spring Boot》", "like" : 15, "publishDate" : ISODate("2021-04-12T13:38:14.829Z") }

聚合框架,就是将上一个操作符处理的结果交个下一个操作符继续处理(这就是Linux中的管道操作),可以使用任意多个操作符,同一个操作符也可以使用多次

// 首先通过$match: 过滤掉不匹配的文档,
// 接着讲满足条件的文档交给$group进行分组,
// 分组后将将分组后的结果交给$sort进行排序,
// 然后将排序后的结果交给$skip处理,跳过前1条,
// 把剩下的文档交给$limit处理,获取最终的聚合结果。

> db.book.aggregate( 
    {
   
     "$match": {
   
     "like": {
   
     "$gte" : 10} }}, 
    {
   
     "$group": {
   
     "_id": "$author", "count": {
   
     "$sum": 1}}},
    {
   
     "$sort": {
   
     "count": -1}},
    {
   
     "$skip": 1},
    {
   
     "$limit": 1} 
)

{
   
      "_id" : "mengday", "count" : 1 }

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: