首页 文章

如何在MongoDB中的$ match中使用聚合运算符(例如$ year或$ dayOfMonth)?

提问于
浏览
15

我有一个包含created_date属性的文档集合 . 我想通过聚合管道发送这些文档来对它们进行一些工作 . 理想情况下,我想在我对其进行任何其他工作之前使用$ match过滤它们以便我可以利用索引但是我无法弄清楚如何使用我的新$ year / $ month / $ dayOfMonth运算符$ match表达式 .

在$ project操作中如何使用运算符有一些例子,但是我担心通过将$ project放在我的管道中的第一步然后我就失去了对索引的访问权限(MongoDB文档表明第一个表达式必须是$ match才能利用索引) .

样本数据:

{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z')
    comments: 48
}
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z')
    comments: 10
}
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z')
    comments: 10
}

我想通过聚合管道来运行它,以获得9月份所有帖子的总评论

{
    aggregate: 'posts',
    pipeline: [
         {$match:
             /*Can I use the $year/$month operators here to match Sept 2012?
             $year:created_date : 2012,
             $month:created_date : 9
             */
             /*or does this have to be 
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt: {$date:'2012-10-01T04:00:00Z'} }
             */
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

这可以工作,但匹配失去了对更复杂查询的任何索引的访问权限:

{
    aggregate: 'posts',
    pipeline: [
         {$project:
              {
                   month : {$month:'$created_date'},
                   year : {$year:'$created_date'}
              }
         },
         {$match:
              {
                   month:9,
                   year: 2012
               }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

3 回答

  • 0

    正如您已经发现的那样,您不能在文档中没有的字段上匹配(它的工作方式与查找工作完全相同),如果您先使用$ project,那么您将无法使用索引 .

    您可以做的是将您的努力结合如下:

    {
        aggregate: 'posts',
        pipeline: [
             {$match: {
                 created_date : 
                      {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                      $lt:  {date:'2012-10-01T04:00:00Z'} 
                      }}
                 }
             },
             {$group:
                 {_id: '0',
                  totalComments:{$sum:'$comments'}
                 }
              }
        ]
     }
    

    以上只为9月汇总,如果你想聚合多个月,你可以举例如:

    {
        aggregate: 'posts',
        pipeline: [
             {$match: {
                 created_date : 
                      { $gte:'2012-07-01T04:00:00Z', 
                        $lt: '2012-10-01T04:00:00Z'
                      }
             },
             {$project: {
                  comments: 1,
                  new_created: {
                            "yr" : {"$year" : "$created_date"},
                            "mo" : {"$month" : "$created_date"}
                         }
                  }
             },
             {$group:
                 {_id: "$new_created",
                  totalComments:{$sum:'$comments'}
                 }
              }
        ]
     }
    

    你会得到类似的东西:

    {
        "result" : [
            {
                "_id" : {
                    "yr" : 2012,
                    "mo" : 7
                },
                "totalComments" : 5
            },
            {
                "_id" : {
                    "yr" : 2012,
                    "mo" : 8
                },
                "totalComments" : 19
            },
            {
                "_id" : {
                    "yr" : 2012,
                    "mo" : 9
                },
                "totalComments" : 21
            }
        ],
        "ok" : 1
    }
    
  • 0

    试试这个;

    db.createCollection("so");
    db.so.remove();
    db.so.insert([
    {
        post_body: 'This is the body of test post 1',
        created_date: ISODate('2012-09-29T05:23:41Z'),
        comments: 48
    },
    {
        post_body: 'This is the body of test post 2',
        created_date: ISODate('2012-09-24T12:34:13Z'),
        comments: 10
    },
    {
        post_body: 'This is the body of test post 3',
        created_date: ISODate('2012-08-16T12:34:13Z'),
        comments: 10
    }
    ]);
    //db.so.find();
    
    db.so.ensureIndex({"created_date":1});
    db.runCommand({
        aggregate:"so",
        pipeline:[
            {
                $match: { // filter only those posts in september
                    created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') }
                }
            },
            {
                $group: {
                    _id: null, // no shared key
                    comments: { $sum: "$comments" } // total comments for all the posts in the pipeline
                }
            },
    ]
    //,explain:true
    });
    

    结果是;

    { "result" : [ { "_id" : null, "comments" : 58 } ], "ok" : 1 }
    

    所以你也可以修改你之前的例子来做这件事,虽然我不确定你为什么要这样做,除非你计划在月中和年份做其他事情;

    {
        aggregate: 'posts',
        pipeline: [
         {$match: { created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') } } },
         {$project:
              {
                   month : {$month:'$created_date'},
                   year : {$year:'$created_date'}
              }
         },
         {$match:
              {
                   month:9,
                   year: 2012
               }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
        ]
     }
    
  • 16

    让我们看看构建一些涉及我们已经熟悉的操作的管道 . 那么,我们将看看以下几个阶段:

    • match - 这是过滤阶段,类似于 find .

    • project

    • sort

    • skip

    • limit

    我们可能会问自己为什么这些阶段是必要的,因为这个功能已经在 MongoDB 查询语言中提供,原因是我们需要这些阶段来支持聚合框架中包含的更复杂的面向分析的功能 . 以下查询简单地等于 find

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, ])
    

    让我们在这个聚合管道中引入一个项目阶段:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $project: {
        _id: 0,
        name: 1,
        founded_year: 1
      }
    }])
    

    我们使用 aggregate 方法来实现聚合框架 . 聚合管道仅仅是一个文档数组 . 每个文件都应规定一个特定的舞台操作员 . 因此,在上面的例子中,我们有一个带有 two 阶段的聚合管道 . $match 阶段将文档一次传递到 $project 阶段 .

    让我们延伸到 limit 阶段:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $limit: 5
    }, {
      $project: {
        _id: 0,
        name: 1
      }
    }])
    

    这会在投出字段之前获取 matching 文档并限制为 five . 因此,投影仅适用于 5 文档 . 假设,如果我们要做这样的事情:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $project: {
        _id: 0,
        name: 1
      }
    }, {
      $limit: 5
    }])
    

    这将获取 matching 文档并将这些大量文档投射到最终限制为 five . 因此,投影正在处理大量文档,最后限制为 5 . 这给了我们一个教训,我们应该 limit the documents to those which are absolutely necessary 传递到下一个阶段 . 现在,让我们来看看 sort 阶段:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $sort: {
        name: 1
      }
    }, {
      $limit: 5
    }, {
      $project: {
        _id: 0,
        name: 1
      }
    }])
    

    这将按名称对所有文档进行排序,并仅提供 5 . 假设,如果我们要做这样的事情:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $limit: 5
    }, {
      $sort: {
        name: 1
      }
    }, {
      $project: {
        _id: 0,
        name: 1
      }
    }])
    

    这将获取第一个 5 文档并对其进行排序 . 让我们添加 skip 阶段:

    db.companies.aggregate([{
      $match: {
        founded_year: 2004
      }
    }, {
      $sort: {
        name: 1
      }
    }, {
      $skip: 10
    }, {
      $limit: 5
    }, {
      $project: {
        _id: 0,
        name: 1
      }
    }, ])
    

    这将对 all 文件进行排序并跳过最初的 10 文档并返回给我们 . 我们应尽量在管道中尽早包含 $match 阶段 . 要使用 $match 阶段过滤文档,我们使用与 find() 相同的语法来构造查询文档(过滤器) .

相关问题