2015-08-18 46 views
4

在MongoDB聚合管道中,每个阶段的记录流发生一次/一批(或)将等待当前阶段完成整个集合,然后再传递给下一个阶段?

对于例如,我有以下样本记录

{name: "Person1", marks: 20} 
{name: "Person2", marks: 20} 
{name: "Person1", marks: 20} 

我共有1000条记录,大约100名学生和我有以下汇总查询

db.classtest.aggregate(
[ 
    {$sort: {name: 1}}, 
    {$group: {_id: '$name', 
      total: {$sum: '$marks'}}}, 
    {$limit: 5} 
]) 

我有以下问题的集合classtest。

  1. 排序顺序在最终结果中丢失。如果我在$ group之后放置另一种排序,则结果会正确排序。这是否意味着$组不保持以前的排序顺序?
  2. 我想将结果限制为5.群组操作是否必须完全完成(对于所有1000条记录)才能通过限制。 (或)小组操作是否通过记录来限制阶段和何时有记录,并在达到限制阶段的要求时停止处理?

我的实际想法是对聚合结果进行分页。在上面的情况下,如果$ group维护排序顺序并仅处理所需的记录数,我想在后续页面查询中应用$match condition {$ge: 'lastPersonName'}

  1. 我不想在$ group之前应用$限制,因为我想要5个学生的结果而不是第5个记录。
  2. 我可能不想使用$ skip,因为这意味着有效地遍历那些许多记录。

回答

2

我已经解决了这个问题,无需维护另一个集合,甚至没有$组遍历整个集合,因此张贴我自己的答案。

正如其他人指出:

  1. $group不保留顺序,因此早期的排序是有很大帮助没有。
  2. $group不做任何优化,即使存在以下$limit,即在整个集合上运行$group

我的用例具有以下独特的功能,它帮我解决这个问题:

  1. 将有最多10条记录每每个学生(1最小值)。
  2. 我也不是很特别的页面大小。前端能够处理不同的页面大小。 以下是我用过的聚合命令。

    db.classtest.aggregate(
    [ 
        {$sort: {name: 1}}, 
        {$limit: 5 * 10}, 
        {$group: {_id: '$name', 
         total: {$sum: '$marks'}}}, 
        {$sort: {_id: 1}} 
    ]) 
    

解释以上。

  1. 如果$sort紧接$limit,框架优化的数据量被发送到下一阶段。请参阅here
  2. 要获得至少5条记录(页面大小),我需要将至少5页(页面大小)* 10(每个学生的最高记录数)= 50条记录传递到$group阶段。因此,最终结果的大小可能在0到50之间。
  3. 如果结果小于5,则不需要进一步分页。
  4. 如果结果大小大于5,则可能有最后一个学生记录没有完全处理(即没有对学生的所有记录进行分组),因此我放弃了结果中的最后一条记录。
  5. 然后在最后一条记录中的名称(保留结果中)被用作后续页面请求中的$匹配条件,如下所示。

    db.classtest.aggregate(
    [ 
        {$match: {name: {$gt: lastRecordName}}} 
        {$sort: {name: 1}}, 
        {$limit: 5 * 10}, 
        {$group: {_id: '$name', 
         total: {$sum: '$marks'}}}, 
        {$sort: {_id: 1}} 
    ]) 
    

在上面,框架会仍然优化$match, $sort and $limit一起作为单一的操作,这是我已经证实通过解释计划。

2
  1. $group为了它的输出文档。”请参见http://docs.mongodb.org/manual/reference/operator/aggregation/group/

  2. $limit限制前一个$sort操作的处理元素的数量,不仅限于传递到下一个阶段的元素的数量。见注,http://docs.mongodb.org/manual/reference/operator/aggregation/limit/

对于你问的第一个问题,我不知道,但它似乎(见1),一个N + 1级能影响阶段的行为N:该限制将限制对其前n个元素进行排序操作,排序操作将不会完成,就好像下面的限制阶段不存在一样。

6

这里要考虑的第一件事是聚合框架与要应用的阶段的“流水线”一起工作以获得结果。如果您熟悉在操作系统的“命令行”或“外壳”上处理事物,那么您可能对“管道”或“操作员”有一些经验。

这里是一个常见的UNIX成语:

ps -ef | grep mongod | tee "out.txt" 

在这种情况下,第一指令这里ps -ef的输出是被“管道”到下一个命令grep mongod这反过来它的输出“管道”到tee out.txt这两个输出到终端以及指定的文件名。这是一个“管道” wher每个阶段“进”了下,并在序列的“订单”都写在。

同样是聚合管道也是如此。 “管道”在这里是实际上“阵列”,这是一组有序的指令,以在所述数据处理结果被传递。

db.classtest.aggregate([ 
    { "$group": { 
     "_id": "$name", 
     "total": { "$sum": "$marks"} 
    }}, 
    { "$sort": { "name": 1 } }, 
    { "$limit": 5 } 
]) 

所以这里所发生的是,所有的集合中的项目首先通过$group处理,以得到他们的总数。没有指定分组的顺序,因此预先排序数据没有多少意义。这样做也没有意义,因为你还没有进入后期阶段。

,那么你会$sort的结果,也$limit要求。

你的下一个“页”的数据,你会非常希望在最后的唯一的名称$match发现,像这样:

db.classtest.aggregate([ 
    { "$match": { "name": { "$gt": lastNameFound } }}, 
    { "$group": { 
     "_id": "$name", 
     "total": { "$sum": "$marks"} 
    }}, 
    { "$sort": { "name": 1 } }, 
    { "$limit": 5 } 
]) 

这不是最好的解决办法,但真的没有这种类型的替代品分组。但是,每次迭代结束时,它都会显着提高“速度”。或者,存储所有unqiue名称(或读取出另一个集合)和“寻呼”,通过该列表与“范围查询”每个聚集声明可能是一个可行的选择,如果你的数据允许的话。

喜欢的东西:

db.classtest.aggregate([ 
    { "$match": { "name": { "$gte": "Allan", "$lte": "David" } }}, 
    { "$group": { 
     "_id": "$name", 
     "total": { "$sum": "$marks"} 
    }}, 
    { "$sort": { "name": 1 } }, 
]) 

不幸的是,没有一个“限制分组,直到X结果”的选项,所以除非你可以用另一个列表工作,那么你基本上分组放弃一切(也可能是AA逐渐每次更小的集合)与您发送的每个聚合查询。