20

我有一个包含 created_date 属性的文档集合。我想通过聚合管道发送这些文档以对它们进行一些工作。理想情况下,我想在对它们进行任何其他工作之前使用 $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'}
             }
          }
    ]
 }
4

3 回答 3

21

正如您已经发现的那样,您不能对文档中没有的字段进行 $match (它的工作方式与 find 的工作方式完全相同),如果您首先使用 $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'}
             }
          }
    ]
 }

上面只给你九月份的聚合,如果你想聚合多个月,你可以例如:

{
    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
}
于 2012-10-02T18:04:07.410 回答
3

让我们看一下构建一些涉及我们已经熟悉的操作的管道。因此,我们将研究以下阶段:

  • match- 这是过滤阶段,类似于find.
  • project
  • sort
  • skip
  • limit

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


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

让我们介绍一下这个聚合管道中的一个项目阶段:


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

我们使用aggregate方法来实现聚合框架。聚合管道只是一个文档数组。每个文件都应规定一个特定的阶段操作员。因此,在上述情况下,我们有一个具有两个阶段的聚合管道。$match阶段是一次将文件传递到阶段$project

让我们扩展到limit阶段:


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

这会在投影字段之前获得匹配的文档并限制为五个。因此,投影仅适用于5 个文档。假设,如果我们要做这样的事情:


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

这会得到匹配的文档并投射那些大量的文档,最终限制为五个。因此,投影正在处理大量文档,最终限制为5。这给了我们一个教训,即我们应该将文件限制在那些绝对需要传递到下一阶段的文件中。现在,让我们看一下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
  }
}, ])

这将对所有文档进行排序并跳过最初的10 个文档并返回给我们。我们应该尽可能$match早地在管道中包含阶段。要使用$match阶段过滤文档,我们使用与构建查询文档(过滤器)相同的语法find()

于 2016-09-16T06:35:39.913 回答
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'}
         }
      }
    ]
 }
于 2012-10-02T17:29:19.770 回答