在MongoDB聚合管道中,从一个阶段到另一个阶段的记录流发生一次/批次(或)将等待当前阶段完成整个集合,然后再将其传递到下一阶段?
例如,我有一个带有以下样本记录的集合classtest
{name: "Person1", marks: 20}
{name: "Person2", marks: 20}
{name: "Person1", marks: 20}
我有大约100名学生的1000条记录,我有以下聚合查询
db.classtest.aggregate(
[
{$sort: {name: 1}},
{$group: {_id: '$name',
total: {$sum: '$marks'}}},
{$limit: 5}
])
我有以下问题。
我的实际想法是对聚合结果进行分页。在上面的场景中,如果$ group维护排序顺序并仅处理所需的记录数,我想在后续页面查询中应用$match condition {$ge: 'lastPersonName'}
。
答案 0 :(得分:6)
这里要考虑的第一件事是聚合框架与要应用的阶段的“管道”一起工作以获得结果。如果您熟悉在操作系统的“命令行”或“shell”上处理事物,那么您可能对“管道”或|
运算符有一些经验。
这是一个常见的unix习语:
ps -ef | grep mongod | tee "out.txt"
在这种情况下,第一个命令ps -ef
的输出被“管道”到下一个命令grep mongod
,而命令tee out.txt
又输出“管道”到db.classtest.aggregate([
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
{ "$limit": 5 }
])
输出到终端以及指定的文件名。这是一个“管道”,每个阶段“馈送”下一个阶段,并按照它们所写的序列的“顺序”。
聚合管道也是如此。这里的“管道”实际上是一个“数组”,它是在处理数据到结果时传递的有序指令集。
db.classtest.aggregate([
{ "$match": { "name": { "$gt": lastNameFound } }},
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
{ "$limit": 5 }
])
所以这里发生的是集合中的所有项目都由$group
首先处理以获得它们的总数。没有指定的“顺序”进行分组,因此预先排序数据没有多大意义。这样做也没有任何意义,因为你还没有进入后期阶段。
对于您的下一个“数据页面”,您最理想的是$match
找到最后一个唯一名称,如下所示:
db.classtest.aggregate([
{ "$match": { "name": { "$gte": "Allan", "$lte": "David" } }},
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
])
这不是最好的解决方案,但实际上并没有这种分组的替代方案。然而,每次迭代结束时,它会显着“更快”。或者,如果您的数据允许,在每个聚合语句中存储所有unqiue名称(或从另一个集合中读取该名称)和“分页”以及“范围查询”可能是一个可行的选择。
类似的东西:
TSimpleServer
不幸的是,没有“限制分组直到x结果”选项,所以除非你可以使用另一个列表,否则你基本上将所有发送的每个聚合查询分组一切(并且可能每次都会逐渐变小)
答案 1 :(得分:4)
我已经解决了这个问题而无需维护另一个集合,甚至没有$ group遍历整个集合,因此发布了我自己的答案。
正如其他人指出的那样:
$group
不保留顺序,因此早期排序没有多大帮助。$group
不进行任何优化,即使有以下$limit
,即在整个集合上运行$group
。我的用例具有以下独特功能,这有助于我解决它:
我对页面大小不太了解。前端能够处理不同的页面大小。 以下是我使用的聚合命令。
db.classtest.aggregate(
[
{$sort: {name: 1}},
{$limit: 5 * 10},
{$group: {_id: '$name',
total: {$sum: '$marks'}}},
{$sort: {_id: 1}}
])
解释上述内容。
$sort
紧接在$limit
之前,框架会优化要发送到下一阶段的数据量。请参阅here $group
阶段。这样,最终结果的大小可以是0到50之间的任何值。然后,在后续页面请求中将最后一条记录中的名称(保留结果中的名称)用作$匹配条件,如下所示。
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 :(得分:2)
“$group
不订购其输出文档。”请参阅http://docs.mongodb.org/manual/reference/operator/aggregation/group/
$limit
限制前一个$sort
操作的已处理元素的数量,而不仅仅是传递给下一个阶段的元素数量。请参阅http://docs.mongodb.org/manual/reference/operator/aggregation/limit/
对于您提出的第一个问题,我不确定,但看起来(见1.)阶段n + 1可以影响阶段n的行为:限制将限制对前n个元素进行排序操作,排序操作不会完成,就像下面的限制阶段不存在一样。
答案 3 :(得分:1)
对组数据mongodb的分页-
在$ group项目中,您不能直接应用分页,但将使用以下技巧,
如果要在组数据上分页-
例如-我希望将产品分组为category,然后每个类别只希望有5种产品
步骤1-在产品表上写汇总,并写groupBY
{ $group: { _id: '$prdCategoryId', products: { $push: '$$ROOT' } } },
步骤2-跳过prdSkip,限制数据的限制,将其传递 动态
{
$project: {
// pagination for products
products: {
$slice: ['$products', prdSkip, prdLimit],
}
}
},
最终查询看起来像- 参数-限制,跳过-用于类别分页 和prdSkip和PrdLimit进行产品分页
db.products.aggregate([
{ $group: { _id: '$prdCategoryId', products: { $push: '$$ROOT' } } },
{
$lookup: {
from: 'categories',
localField: '_id',
foreignField: '_id',
as: 'categoryProducts',
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [{ $arrayElemAt: ['$categoryProducts', 0] }, '$$ROOT'],
},
},
},
{
$project: {
// pagination for products
products: {
$slice: ['$products', prdSkip, prdLimit],
},
_id: 1,
catName: 1,
catDescription: 1,
},
},
])
.limit(limit) // pagination for category
.skip(skip);
我在这里使用replaceRoot来提取类别。