例如,假设我有{10,000}个已排序的文档,我希望aggregate()
开启。但是我想将它们分成四分位数:前25%,25% - 50%,50% - 75%,最低25%。在一个管道中有没有办法解决这个问题,而不必为每个四分位数做4个独立的管道?
类似的东西:
aggregate()
- Transform into {quartile1: [list of docs], quartile2: [list of docs], ...}
- Run other pipeline commands
或者我是否需要运行4个单独的aggregate()
管道?
谢谢!
答案 0 :(得分:2)
对于你所问的问题,“聚合框架可以吗?”,那么答案就是不可以。另一方面,你可以用mapReduce做这样的事情。但我想提出的真实情况是这种情况的可靠性,以及“有什么意义?”。
在这里表达怀疑主义的最佳方式是充分解释事情。
聚合框架不能做这种事情,因为它在处理10,000个文档的过程中没有“当前它在哪里”的概念。为此,您需要某种“变量”,当您处理每个“已排序”的项目时,该变量会递增。
您可以使用该方法根据您“排序”的值“标记”项目。但问题仍然是“你怎么知道”特定值在整个结果集中的排名。因此,除非有明确的方法,否则你不能投射这样一个领域。
只有当您准备使用不一定是所有结果的“四分之一”的“设定范围”时,您才能使用.aggregate()
执行此操作:
db.collection.aggregate([
{ "$project": {
"grouping": {
"$cond": [
{ "$lt": [ "$score", 25 ] },
3,
{ "$cond": [
{ "$lt": [ "$score", 50 ] },
2,
{ "$cond": [
{ "$lt": [ "$score", 75 ] },
1,
0
]}
]}
]
},
"score": 1,
"otherField": 1
}},
{ "$sort": { "grouping" 1, "score": -1 }
])
另一方面,.mapReduce()
可以访问这样的全局变量。因此,基本上可以检查计数器,以查看它是否在您预期的分组中。基本形式:
db.collection.mapReduce(
function() {
counter++;
if ( counter % ( total / 4 ) == 0 )
grouping++;
var id = this._id;
delete this._id;
emit({ "grouping": grouping, "_id": id },this);
},
function() {}, // no need for a reducer
{
"out": { "replace": "results" },
"scope": { "counter": 0, "grouping": 0, "total": 10000 },
"sort": { "score": -1 }
}
)
它基本上做你想要的。但不是以非常灵活的方式或非常可靠的方式。主要是因为在大多数现实情况下,并不能保证始终有10,000个结果,特别是如果运行一个带条件的查询来计算,另一个查询将结果“标记”到它们的分组中。
因此,考虑到这里根本没有发生真正的“聚合”,那么最好的方法就是简单地将数据查询到列表中:
var cursor = db.collection.find({}).sort({ "score": -1 });
var total = cursor.count();
var counter = 0,
grouping = 0;
cursor.forEach(function(doc) {
counter++;
if ( counter % ( total / 4 ) == 0 )
grouping++;
doc._id = { "grouping": grouping, "_id": doc._id };
// Do something with "doc"
});
不是很优雅,但指出了基本技术。
另外要注意,你建议的数组[]
并不是一个好主意。即使在10,000文档场景中,在单个文档响应中产生的2,500个元素阵列和基本上10,000个项目,也可能“炸毁”16MB BSON限制。至少它不是很容易管理,用光标更好。
因此,您既可以选择服务器来“标记”这些项目,也可以在阅读时“标记”它们。至少在后一种情况下,您可以访问结果的“光标”
答案 1 :(得分:0)
我认为需要沿着mongo docs中找到的4条管道。
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
但是针对所有正常的数据库规则,请考虑双重浸入或两次输入数据。一次用于基数场,另一次用于四分位场。这种方法很难,但允许快速读取;一个人可以在索引字段上进行简单的查找,并执行单个聚合。
{name: cartman, score: 56, quartile: 3 }
{name: kenny, score: 36, quartile: 2 }
{name: kyle, score: 76, quartile: 4 }
db.scores.find( {"quartile" : 3 });
db.scores.aggregate( [
{ $group: { _id: null, count: { $quartile: 1 } } }
] );