例如,假设我有 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()
管道?
谢谢!
对于你所问的,"聚合框架可以解决这个问题吗?",那么答案是否定的,它不能。另一方面,你可以用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 限制。至少它不是很易于管理,最好用光标处理。
因此,您可以选择服务器来"标记"这些项目,也可以在读取它们时"标记"它们。至少在后一种情况下,您可以访问结果的"光标"
我认为需要 4 个管道,就像在 MONGO 文档中找到的那样。
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
但与所有正常的数据库规则相反,请考虑双重浸入或输入数据两次。 一次用于基数字段,一次用于四分位数字段。 这种方法很模糊,但允许快速读取; 可以在索引字段上进行简单的查找,并执行单个AGG。
{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 } } }
] );