我有一个查询,我需要返回10个"类型A"记录,同时返回所有其他记录。我怎么能做到这一点?
更新:不可否认,我可以通过两个查询执行此操作,但我想避免这种情况,如果可能的话,认为它可以减少开销,并且可能更高效。我的查询已经是一个聚合查询,它考虑了两种记录,我只需要限制结果中一种记录的数量。
更新:以下是突出显示问题的示例查询:
db.books.aggregate([
{$geoNear: {near: [-118.09771, 33.89244], distanceField: "distance", spherical: true}},
{$match: {"type": "Fiction"}},
{$project: {
'title': 1,
'author': 1,
'type': 1,
'typeSortOrder':
{$add: [
{$cond: [{$eq: ['$type', "Fiction"]}, 1, 0]},
{$cond: [{$eq: ['$type', "Science"]}, 0, 0]},
{$cond: [{$eq: ['$type', "Horror"]}, 3, 0]}
]},
}},
{$sort: {'typeSortOrder'}},
{$limit: 10}
])
db.books.aggregate([
{$geoNear: {near: [-118.09771, 33.89244], distanceField: "distance", spherical: true}},
{$match: {"type": "Horror"}},
{$project: {
'title': 1,
'author': 1,
'type': 1,
'typeSortOrder':
{$add: [
{$cond: [{$eq: ['$type', "Fiction"]}, 1, 0]},
{$cond: [{$eq: ['$type', "Science"]}, 0, 0]},
{$cond: [{$eq: ['$type', "Horror"]}, 3, 0]}
]},
}},
{$sort: {'typeSortOrder'}},
{$limit: 10}
])
db.books.aggregate([
{$geoNear: {near: [-118.09771, 33.89244], distanceField: "distance", spherical: true}},
{$match: {"type": "Science"}},
{$project: {
'title': 1,
'author': 1,
'type': 1,
'typeSortOrder':
{$add: [
{$cond: [{$eq: ['$type', "Fiction"]}, 1, 0]},
{$cond: [{$eq: ['$type', "Science"]}, 0, 0]},
{$cond: [{$eq: ['$type', "Horror"]}, 3, 0]}
]},
}},
{$sort: {'typeSortOrder'}},
{$limit: 10}
])
我想在一个查询中返回所有这些记录,但是将类型限制为任何类别中的最多10个。 我意识到当查询被这样分解时,typeSortOrder不需要是有条件的,我最初在那里查询是一个查询时(我想回到这里)。 / p>
答案 0 :(得分:4)
我不认为目前(2.6)可以使用一个聚合管道。很难给出一个关于其原因的精确论据,但基本上聚合管道一次执行一个文档流的转换。在流的状态管道中没有意识到,这是你需要确定你已达到A,B&B的限制等,并需要删除相同类型的其他文档。 $group
确实将多个文档放在一起,并允许其汇总的字段值影响生成的组文档($sum
,$avg
等)。也许这是有道理的,但它必然不严谨,因为您可以添加简单的操作,以便可以根据类型进行限制,例如,向$push x
添加$group
累加器如果被推送的数组少于x个元素,则仅推送该值。
即使我确实有办法,我也建议只进行两次聚合。保持简单。
答案 1 :(得分:2)
这是MongoDB不支持的子查询/连接的经典案例。所有联接和子查询类操作都需要在应用程序逻辑中实现。因此,多个查询是您最好的选择。如果您有类型索引,那么多查询方法的性能应该很好。
或者,您可以编写单个聚合查询,减去类型匹配和限制子句,然后在应用程序逻辑中处理流以限制每种类型的文档。 对于大型结果集,此方法的性能较低,因为文档可能以随机顺序返回。然后,您的限制逻辑将需要遍历整个结果集。
答案 2 :(得分:2)
我猜你可以在游标上使用cursor.limit()来指定游标返回的最大文档数。 limit()类似于SQL数据库中的LIMIT语句。 在从数据库中检索任何文档之前,必须对游标应用limit()。
游标中的限制功能可用于限制查找中的记录数。
我想这个例子应该有所帮助:
var myCursor = db.bios.find( );
db.bios.find().limit( 5 )
答案 3 :(得分:2)
这里的结果并非不可能,但也可能不切实际。已经做了一般性的注意事项,你不能切片"数组或其他"限制"结果的数量推到一个。并且按照"类型"执行此操作的方法本质上是使用数组。
"不切实际" part通常是关于结果的数量,其中太大的结果集会在"分组"时爆炸BSON文档限制。但是,我将在您的" geo search"上提出一些其他建议。最终目标是返回每个"类型"的10个结果。至多。
首先要考虑并理解这个问题,让我们看一下简化的" set"返回"前2个结果所需的数据和管道代码"来自每种类型:
{ "title": "Title 1", "author": "Author 1", "type": "Fiction", "distance": 1 },
{ "title": "Title 2", "author": "Author 2", "type": "Fiction", "distance": 2 },
{ "title": "Title 3", "author": "Author 3", "type": "Fiction", "distance": 3 },
{ "title": "Title 4", "author": "Author 4", "type": "Science", "distance": 1 },
{ "title": "Title 5", "author": "Author 5", "type": "Science", "distance": 2 },
{ "title": "Title 6", "author": "Author 6", "type": "Science", "distance": 3 },
{ "title": "Title 7", "author": "Author 7", "type": "Horror", "distance": 1 }
这是数据的简化视图,在某种程度上代表了初始查询后的文档状态。现在就是如何使用聚合管道来获得最近的"每个"类型"的两个结果:
db.books.aggregate([
{ "$sort": { "type": 1, "distance": 1 } },
{ "$group": {
"_id": "$type",
"1": {
"$first": {
"_id": "$_id",
"title": "$title",
"author": "$author",
"distance": "$distance"
}
},
"books": {
"$push": {
"_id": "$_id",
"title": "$title",
"author": "$author",
"distance": "$distance"
}
}
}},
{ "$project": {
"1": 1,
"books": {
"$cond": [
{ "$eq": [ { "$size": "$books" }, 1 ] },
{ "$literal": [false] },
"$books"
]
}
}},
{ "$unwind": "$books" },
{ "$project": {
"1": 1,
"books": 1,
"seen": { "$eq": [ "$1", "$books" ] }
}},
{ "$sort": { "_id": 1, "seen": 1 } },
{ "$group": {
"_id": "$_id",
"1": { "$first": "$1" },
"2": { "$first": "$books" },
"books": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$books", false ]
}
}
}},
{ "$project": {
"1": 1,
"2": 2,
"pos": { "$literal": [1,2] }
}},
{ "$unwind": "$pos" },
{ "$group": {
"_id": "$_id",
"books": {
"$push": {
"$cond": [
{ "$eq": [ "$pos", 1 ] },
"$1",
{ "$cond": [
{ "$eq": [ "$pos", 2 ] },
"$2",
false
]}
]
}
}
}},
{ "$unwind": "$books" },
{ "$match": { "books": { "$ne": false } } },
{ "$project": {
"_id": "$books._id",
"title": "$books.title",
"author": "$books.author",
"type": "$_id",
"distance": "$books.distance",
"sortOrder": {
"$add": [
{ "$cond": [ { "$eq": [ "$_id", "Fiction" ] }, 1, 0 ] },
{ "$cond": [ { "$eq": [ "$_id", "Science" ] }, 0, 0 ] },
{ "$cond": [ { "$eq": [ "$_id", "Horror" ] }, 3, 0 ] }
]
}
}},
{ "$sort": { "sortOrder": 1 } }
])
当然这只是两个结果,但它概述了获取n
结果的过程,这自然是在生成的管道代码中完成的。在进入代码之前,该过程值得一试。
在任何查询之后,这里要做的第一件事是$sort
结果,而这要基本上通过"分组键"这是"类型"并且通过"距离"这样"最近的"物品在上面。
这一点的原因显示在$group
阶段,将重复。所做的主要是"弹出每个分组堆栈的$first
结果。因此,其他文档不会丢失,使用$push
将它们放在一个数组中。
为了安全起见,下一阶段实际上只需要在"第一步"之后,但可以选择添加以便在重复中进行类似的过滤。这里的主要检查是得到的"数组"大于一个项目。如果不是,则将内容替换为单个false值。其原因即将变得明显。
在此之后"第一步"真正的重复循环生物,然后该阵列被去标准化"使用$unwind
,然后$project
制作,以便匹配"最后一次见过"。
由于只有一个文件符合这一条件,因此结果再次排序"排序"为了漂浮"看不见的"文档到顶部,当然保持分组顺序。接下来的事情类似于第一个$group
步骤,但保持任何保持位置并且首先看不到"文件被#34;弹出堆栈"试。
"见过的文件"然后被推回到数组而不是作为自身,而是作为false
的值。这不会与保留的值相匹配,这通常是处理这种情况的方式而不是“破坏性的”#34;如果没有足够的匹配来覆盖所需的n
结果,那么您不希望操作失败的数组内容。
完成后清理,下一次"预测"将数组添加到现在按"类型"分组的最终文档中。代表所需n
个结果中的每个位置。展开此数组时,文档可以再次组合在一起,但现在所有文档都在一个数组中
可能包含多个false
值,但n
个元素长。
最后再次展开数组,使用$match
过滤掉false
值,并投影到所需的文档表单。
前面提到的问题是过滤结果的数量,因为可以推送到数组中的结果数量存在实际限制。这主要是BSON限制,但即使仍处于限制范围内,您也不会真正想要1000件物品。
这里的诀窍是保持最初的#34;匹配"足够小的切片操作"变得实用。 $geoNear
管道流程中有一些东西可以使这成为可能。
显而易见的是limit
。默认情况下,这是100,但您显然希望获得以下范围内的内容:
(您可能匹配的类别数量)X(必填项)
但如果这实际上是一个不在1000年代的数字,那么这里已经有了一些帮助。
其他人是maxDistance
和minDistance
,其中基本上你将上下限放在"远离"寻找。最大界限是一般限制器,而最小界限在" paging"时是有用的,这是下一个帮手。
当"向上分页"时,您可以使用query
参数来排除文档的_id
值"已经看过"使用$nin
查询。以同样的方式,minDistance
可以填充"最后一次见到"最大距离,或至少最小的最大距离"类型"。这允许一些概念来过滤已经被"看到"并获得另一页。
这本身就是一个话题,但是为了使这个过程切合实际,这些是减少初始匹配的一般事项。
返回" 10的一般问题最多,每种类型"显然需要一些代码才能生成流水线阶段。没有人愿意输入,实际上你可能想在某个时候改变这个数字。
现在到了可以生成怪物管道的代码。 JavaScript中的所有代码,但原则上易于翻译:
var coords = [-118.09771, 33.89244];
var key = "$type";
var val = {
"_id": "$_id",
"title": "$title",
"author": "$author",
"distance": "$distance"
};
var maxLen = 10;
var stack = [];
var pipe = [];
var fproj = { "$project": { "pos": { "$literal": [] } } };
pipe.push({ "$geoNear": {
"near": coords,
"distanceField": "distance",
"spherical": true
}});
pipe.push({ "$sort": {
"type": 1, "distance": 1
}});
for ( var x = 1; x <= maxLen; x++ ) {
fproj["$project"][""+x] = 1;
fproj["$project"]["pos"]["$literal"].push( x );
var rec = {
"$cond": [ { "$eq": [ "$pos", x ] }, "$"+x ]
};
if ( stack.length == 0 ) {
rec["$cond"].push( false );
} else {
lval = stack.pop();
rec["$cond"].push( lval );
}
stack.push( rec );
if ( x == 1) {
pipe.push({ "$group": {
"_id": key,
"1": { "$first": val },
"books": { "$push": val }
}});
pipe.push({ "$project": {
"1": 1,
"books": {
"$cond": [
{ "$eq": [ { "$size": "$books" }, 1 ] },
{ "$literal": [false] },
"$books"
]
}
}});
} else {
pipe.push({ "$unwind": "$books" });
var proj = {
"$project": {
"books": 1
}
};
proj["$project"]["seen"] = { "$eq": [ "$"+(x-1), "$books" ] };
var grp = {
"$group": {
"_id": "$_id",
"books": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$books", false ]
}
}
}
};
for ( n=x; n >= 1; n-- ) {
if ( n != x )
proj["$project"][""+n] = 1;
grp["$group"][""+n] = ( n == x ) ? { "$first": "$books" } : { "$first": "$"+n };
}
pipe.push( proj );
pipe.push({ "$sort": { "_id": 1, "seen": 1 } });
pipe.push(grp);
}
}
pipe.push(fproj);
pipe.push({ "$unwind": "$pos" });
pipe.push({
"$group": {
"_id": "$_id",
"msgs": { "$push": stack[0] }
}
});
pipe.push({ "$unwind": "$books" });
pipe.push({ "$match": { "books": { "$ne": false } }});
pipe.push({
"$project": {
"_id": "$books._id",
"title": "$books.title",
"author": "$books.author",
"type": "$_id",
"distance": "$books",
"sortOrder": {
"$add": [
{ "$cond": [ { "$eq": [ "$_id", "Fiction" ] }, 1, 0 ] },
{ "$cond": [ { "$eq": [ "$_id", "Science" ] }, 0, 0 ] },
{ "$cond": [ { "$eq": [ "$_id", "Horror" ] }, 3, 0 ] },
]
}
}
});
pipe.push({ "$sort": { "sortOrder": 1, "distance": 1 } });
当然,这里的最终结果以及上述所有问题的一般问题是你真的只想要&#34;前10&#34;每个&#34;类型&#34;回来。聚合管道将会这样做,但代价是保持10以上,然后从堆栈弹出#34;到达10点。
另一种方法是蛮力&#34;这与mapReduce和&#34;全局范围&#34;变量。不是很好,因为结果全部在数组中,但它可能是一个实用的方法:
db.collection.mapReduce(
function () {
if ( !stash.hasOwnProperty(this.type) ) {
stash[this.type] = [];
}
if ( stash[this.type.length < maxLen ) {
stash[this.type].push({
"title": this.title,
"author": this.author,
"type": this.type,
"distance": this.distance
});
emit( this.type, 1 );
}
},
function(key,values) {
return 1; // really just want to keep the keys
},
{
"query": {
"location": {
"$nearSphere": [-118.09771, 33.89244]
}
},
"scope": { "stash": {}, "maxLen": 10 },
"finalize": function(key,value) {
return { "msgs": stash[key] };
},
"out": { "inline": 1 }
}
)
这是一个真正的作弊,只使用&#34;全球范围&#34;保持其键为分组键的单个对象。将结果推送到该全局对象中的数组上,直到达到最大长度。结果已按最近的顺序排序,因此映射器只是在每个键达到10之后放弃对当前文档执行任何操作。
由于每个键只发出一个文档,因此不会调用reducer。最终确定然后&#34;拉出&#34;来自全局的值并将其返回到结果中。
很简单,但是如果你真的需要它们,你当然不会拥有所有$geoNear
个选项,而且这个表单的硬限制为100个文档作为初始查询的输出。