我正在尝试查找符合特定条件的所有记录,在这种情况下,_id
是特定值,然后仅返回前2个结果(按名称字段排序)。
这就是我所拥有的
db.getCollection('col1').aggregate([
{$match: {fk: {$in: [1, 2]}}},
{$sort: {fk: 1, name: -1}},
{$group: {_id: "$fk", items: {$push: "$$ROOT"} }},
{$project: {items: {$slice: ["$items", 2]} }}
])
它可以工作,但是,不能保证。根据{{3}},$group
不保证文档顺序。
这也意味着此处和其他地方建议的所有解决方案,建议先使用$unwind
,然后依次使用$sort
和$group
,对于同样的方法原因。
用Mongo(任何版本)完成此操作的最佳方法是什么?我已经看到建议可以在$project
阶段完成此操作的建议,但是我不太确定该怎么做。
答案 0 :(得分:2)
您正确地说$group
的结果从未排序。
$ group不订购其输出文档。
因此做一个;
{$sort: {fk: 1}}
然后与
分组{$group: {_id: "$fk", ... }},
将是浪费的精力。
但是在$group
阶段之前,name: -1
会进行排序的银衬里。由于您使用的是$push
(而不是$addToSet
),因此插入的对象将保留其在items
结果中新创建的$group
数组中的顺序。您可以看到此行为here (copy of your pipeline)
items
数组将总是 具有;
"items": [
{
..
"name": "Michael"
},
{
..
"name": "George"
}
]
以相同顺序,因此您的嵌套数组排序是非问题!尽管我无法在文档中找到确切的报价来确认此行为,但是您可以检查;
$group
,其中$addToSet
的描述中带有"Order of the array elements is undefined."
,而类似的运算符$push
没有,这可能是间接的证据? :)只需对您的管道进行简单的修改,即可将fk: 1
的类别从前$group
阶段移至后$group
阶段;
db.getCollection('col1').aggregate([
{$match: {fk: {$in: [1, 2]}}},
{$sort: {name: -1}},
{$group: {_id: "$fk", items: {$push: "$$ROOT"} }},
{$sort: {_id: 1}},
{$project: {items: {$slice: ["$items", 2]} }}
])
应该足以固定主要结果数组的顺序。在mongoplayground
上检查答案 1 :(得分:1)
$ group不能保证文档顺序,但是可以将分组文档按每个存储区的排序顺序保留。因此,在您的情况下,即使$ group阶段之后的文档未按fk排序,但每个组(项目)也将按名称降序排序。如果您想按fk排序文档,只需在$ group阶段之后添加{$sort:{fk:1}}
如果需要,您还可以根据匹配查询中传递的值的顺序进行排序,方法是为每个文档添加一个额外的字段。
db.getCollection('col1').aggregate([
{$match: {fk: {$in: [1, 2]}}},
{$addField:{ifk:{$indexOfArray:[[1, 2],"$fk"]}}},
{$sort: {ifk: 1, name: -1}},
{$group: {_id: "$ifk", items: {$push: "$$ROOT"}}},
{$sort: {_id : 1}},
{$project: {items: {$slice: ["$items", 2]}}}
])
更新以允许在没有组运算符的情况下进行数组排序:我发现jira
将允许对数组进行排序。
您可以在$project
以下阶段尝试对数组进行排序。也许有多种方法可以执行此操作。这应该将名称降序排列。工作正常,但解决方案较慢。
{"$project":{"items":{"$reduce":{
"input":"$items",
"initialValue":[],
"in":{"$let":{
"vars":{"othis":"$$this","ovalue":"$$value"},
"in":{"$let":{
"vars":{
//return index as 0 when comparing the first value with initial value (empty) or else return the index of value from the accumlator array which is closest and less than the current value.
"index":{"$cond":{
"if":{"$eq":["$$ovalue",[]]},
"then":0,
"else":{"$reduce":{
"input":"$$ovalue",
"initialValue":0,
"in":{"$cond":{
"if":{"$lt":["$$othis.name","$$this.name"]},
"then":{"$add":["$$value",1]},
"else":"$$value"}}}}
}}
},
//insert the current value at the found index
"in":{"$concatArrays":[
{"$slice":["$$ovalue","$$index"]},
["$$othis"],
{"$slice":["$$ovalue",{"$subtract":["$$index",{"$size":"$$ovalue"}]}]}]}
}}}}
}}}}
一个简单的示例,演示每个迭代的工作原理
db.b.insert({"items":[2,5,4,7,6,3]});
othis ovalue index concat arrays (parts with counts) return value
2 [] 0 [],0 [2] [],0 [2]
5 [2] 0 [],0 [5] [2],-1 [5,2]
4 [5,2] 1 [5],1 [4] [2],-1 [5,4,2]
7 [5,4,2] 0 [],0 [7] [5,4,2],-3 [7,5,4,2]
6 [7,5,4,2] 1 [7],1 [6] [5,4,2],-3 [7,6,5,4,2]
3 [7,6,5,4,2] 4 [7,6,5,4],4 [3] [2],-1 [7,6,5,4,3,2]
答案 2 :(得分:1)
该问题中有一个红色的鲱鱼,因为$group
确实保证将按顺序处理传入的文档(这就是为什么您必须在$group
之前对它们进行排序才能获得有序的数组),但是您建议这样做的方式存在问题,因为将所有文档归为一个单一的组是(a)低效的,并且(b)可能超过最大文档大小。
由于您只想要前两个,所以对于每个唯一的fk
值,最有效的方法是通过使用$lookup
的“子查询”,如下所示:
db.coll.aggregate([
{$match: {fk: {$in: [1, 2]}}},
{$group:{_id:"$fk"}},
{$sort: {_id: 1}},
{$lookup:{
from:"coll",
as:"items",
let:{fk:"$_id"},
pipeline:[
{$match:{$expr:{$eq:["$fk","$$fk"]}}},
{$sort:{name:-1}},
{$limit:2},
{$project:{_id:0, fk:1, name:1}}
]
}}
])
假设您必须在{fk:1, name:-1}
上有一个索引,才能在提议的代码中进行有效的排序,此处的前两个阶段将通过DISTINCT_SCAN
计划使用该索引,这非常有效,并且对于每个其中$lookup
将使用相同的索引按fk
的单个值进行过滤,并返回已排序并限制在前两个中的结果。至少在服务器实现https://jira.mongodb.org/browse/SERVER-9377之前,这将是最有效的方法。