MongoDB聚合查询中限制数组的数量

3
我正在尝试编写一个查询,以返回每个类别中的前X个术语 - 例如前5个、前10个等等。每个术语都有一个相关的类别,并且在得到另一个stackoverflow问题的帮助后,我已经成功地做到了这一点。
db.collection.aggregate([
  { 
    $group : { 
      _id :  { 
        category: "$uri.category",
        term: "$uri.term",
      },
      total: { $sum : 1 } 
    }
  },
  { $sort : { total : -1 } },
  { 
    $group : { 
        _id :  "$_id.category",
        terms: { 
            $push: { 
                term: "$_id.term",
                total: "$total"
            }
        }
     }
  }
]);

上述查询可行,并返回类似以下数据的结果:
[
 { category: "movies", 
   terms: [ { term: "movie 1", total: 5000 }, { term: "movie 2", total: 200 } ... ]
 },
 { category: "sports", 
   terms: [ { term: "football 1", total: 4000 }, { term: "tennis 2", total: 250 } ... ]
 },
]

然而,我正在尝试将术语数组限制为固定数量,例如5或10-这将对应于每个类别的X次搜索。我一直在尝试各种选项,比如在$push中添加$slice来缩小术语数组,但没有成功。
使用聚合框架可以实现这个目标吗?还是我应该考虑其他方法?
3个回答

8

从MongoDb版本3.1.6开始,您现在可以在$project阶段上进行切片:

{
    $project: {
        terms: {
            $slice: ["$terms", 0, 10]
        }
    }
}

如果您想将$pushed的项数限制为10个,这里有一个问题:https://jira.mongodb.org/browse/SERVER-6074

3
自 Mongodb 2.6 版本开始,使用 $slice$push.aggregate() 函数/命令中限制数组大小的功能已不受支持。在 MongoDb 问题跟踪器上有一个 功能请求
我会将聚合结果输出到集合中,然后更新集合。

示例:

设置:

use test;
var rInt = function(x) {
    return 1 + ~~(Math.random() * x);
};
var rObj = function() {
    return {
        "timestamp": new Date(),
        "category": "movies" + rInt(5),
        "term": "my movie" + rInt(20)
    }
};
for (var i = 0, l = 100; i < l; i++) {
    db.al.insert(rObj());
}

聚合查询

db.al_out.drop();

db.al.aggregate([
  { 
    $group : { 
      _id :  { 
        category: "$category",
        term: "$term",
      },
      total: { $sum : 1 } 
    }
  },
  { $sort : { total : -1 } },
  { 
    $group : { 
        _id :  "$_id.category",
        terms: { 
            $push: { 
                term: "$_id.term",
                total: "$total"
            }
        }
     }
  }
  ,{ $out : "al_out" }  // output the documents to `db.al_out`
]);

// limit the size of terms to 3 elements.
db.al_out.update( {}, {
  $push : {
    terms : { 
      $each : [],
      $slice : 3 
    }
  }
}, {
  multi:true
});

结果:

db.al_out.find();

{ "_id" : "movies1", "terms" : [ { "term" : "my movie7", "total" : 3 }, { "term" : "my movie6", "total" : 3 }, { "term" : "my movie17", "total" : 2 } ] }
{ "_id" : "movies2", "terms" : [ { "term" : "my movie3", "total" : 4 }, { "term" : "my movie11", "total" : 2 }, { "term" : "my movie2", "total" : 2 } ] }
{ "_id" : "movies4", "terms" : [ { "term" : "my movie9", "total" : 3 }, { "term" : "my movie1", "total" : 3 }, { "term" : "my movie7", "total" : 2 } ] }
{ "_id" : "movies3", "terms" : [ { "term" : "my movie19", "total" : 5 }, { "term" : "my movie8", "total" : 4 }, { "term" : "my movie14", "total" : 4 } ] }
{ "_id" : "movies5", "terms" : [ { "term" : "my movie7", "total" : 6 }, { "term" : "my movie17", "total" : 4 }, { "term" : "my movie3", "total" : 2 } ] }

不需要保存额外的集合,您可以像Craig在下一个答案中描述的那样使用project和slice。 - Kasra

2
我建议在 $sort 和 $group 之间添加 $limit 阶段:
{ $limit : 5 },

这将限制被推入数组的文档数量为5。这也将限制在排序中维护的文档总数,从而提高整体性能:

当$sort紧接着$limit出现在管道中时,$sort操作只会在进行过程中维护前n个结果,其中n是指定的限制,MongoDB只需要在内存中存储n个项目。

http://docs.mongodb.org/manual/reference/operator/aggregation/limit/


5
虽然你说的 { $limit : 5 } 可以将结果限制在5个,但是我想要每个分类别的前5个,因此这并不能解决问题,因为它会对所有分类别进行限制。 - clangers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接