MongoDB的$ in子句是否保证顺序?
当使用MongoDB的$in
子句时,返回的文档的顺序是否总是对应于数组参数的顺序?
如上所述,$ in子句中数组参数的顺序并不反映文档检索顺序。 当然,这将是自然顺序或按所示的选定索引顺序。
如果你需要保存这个命令,那么你基本上有两个select。
所以我们假设你的文档中的_id
的值与一个将被传递给$in
的数组匹配[ _id
[ 4, 2, 8 ]
。
使用聚合的方法
var list = [ 4, 2, 8 ]; db.collection.aggregate([ // Match the selected documents by "_id" { "$match": { "_id": { "$in": [ 4, 2, 8 ] }, }, // Project a "weight" to each document { "$project": { "weight": { "$cond": [ { "$eq": [ "$_id", 4 ] }, 1, { "$cond": [ { "$eq": [ "$_id", 2 ] }, 2, 3 ]} ]} }}, // Sort the results { "$sort": { "weight": 1 } } ])
所以这将是扩大的forms。 这里基本上是这样的,就像在数组中传递给$in
的数组一样,也构造一个“嵌套的” $cond
语句来testing这些值并分配一个合适的权重。 由于“权重”值反映了数组中元素的顺序,因此可以将该值传递给sorting阶段,以便按照所需顺序获得结果。
当然,你实际上在代码中“build立”pipe道语句,就像这样:
var list = [ 4, 2, 8 ]; var stack = []; for (var i = list.length - 1; i > 0; i--) { var rec = { "$cond": [ { "$eq": [ "$_id", list[i-1] ] }, i ] }; if ( stack.length == 0 ) { rec["$cond"].push( i+1 ); } else { var lval = stack.pop(); rec["$cond"].push( lval ); } stack.push( rec ); } var pipeline = [ { "$match": { "_id": { "$in": list } }}, { "$project": { "weight": stack[0] }}, { "$sort": { "weight": 1 } } ]; db.collection.aggregate( pipeline );
使用mapReduce的方法
当然,如果这一切似乎对你的敏感性有很大的帮助,那么你可以用mapReduce来做同样的事情,看起来比较简单,但是运行速度可能会稍慢。
var list = [ 4, 2, 8 ]; db.collection.mapReduce( function () { var order = inputs.indexOf(this._id); emit( order, { doc: this } ); }, function() {}, { "out": { "inline": 1 }, "query": { "_id": { "$in": list } }, "scope": { "inputs": list } , "finalize": function (key, value) { return value.doc; } } )
而这基本上依赖于发出的“键”值是在input数组中如何出现的“索引顺序”。
所以这些基本上是你维护一个input列表的顺序到一个$in
条件,你已经有了一个确定的顺序列表。
如果你不想使用aggregate
,另一个解决scheme是使用find
,然后使用array#sort
对文档结果客户端进行array#sort
:
如果$in
值是像数字这样的基本types,则可以使用如下的方法:
var ids = [4, 2, 8, 1, 9, 3, 5, 6]; MyModel.find({ _id: { $in: ids } }).exec(function(err, docs) { docs.sort(function(a, b) { // Sort docs by the order of their _id values in ids. return ids.indexOf(a._id) - ids.indexOf(b._id); }); });
如果$in
值是像ObjectId
这样的非原始types,那么需要另一种方法,因为indexOf
在这种情况下通过引用进行比较。
如果您使用的是Node.js 4.x +,则可以使用Array#findIndex
和ObjectID#equals
通过将sort
函数更改为:
docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - ids.findIndex(id => b._id.equals(id)));
或者用任何Node.js版本,用下划线/ lodash的findIndex
:
docs.sort(function (a, b) { return _.findIndex(ids, function (id) { return a._id.equals(id); }) - _.findIndex(ids, function (id) { return b._id.equals(id); }); });
另一种使用Aggregation查询的方式只适用于MongoDB verion> 3.4 –
信贷去这个不错的博客文章 。
按此顺序提取示例文档 –
var order = [ "David", "Charlie", "Tess" ];
查询 –
var query = [ {$match: {name: {$in: order}}}, {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}}, {$sort: {"__order": 1}} ]; var result = db.users.aggregate(query);
post的另一个引用解释了这些聚合运算符 –
“$ addFields”阶段是3.4中的新function,它允许您在不知道所有其他现有字段的情况下将“$ project”新字段添加到现有文档。 新的“$ indexOfArray”expression式返回给定数组中特定元素的位置。
基本上, addToSet
操作符会在每个文档find它时附加一个新的order
字段,这个order
字段表示我们提供的数组的原始顺序。 然后,我们简单地根据这个字段对文档进行sorting。
总是? 决不。 顺序总是相同的:未定义(可能是文档存储的物理顺序)。 除非你把它分类。
与JonnyHK的解决scheme类似,您可以使用EcmaScript 2015中map
和Array.prototype.find
函数的组合来重新sorting客户端find
返回的文档(如果客户端在JavaScript中):
Collection.find({ _id: { $in: idArray } }).toArray(function(err, res) { var orderedResults = idArray.map(function(id) { return res.find(function(document) { return document._id.equals(id); }); }); });
几个注意事项:
- 上面的代码是使用Mongo Node驱动程序,而不是 Mongoose
-
idArray
是一个ObjectId
的数组 - 我还没有testing过这种方法的性能,但是如果你需要操纵每个返回的项目(这是很常见的),你可以在
map
callback中做到这一点,以简化你的代码。
我知道这是一个旧的线程,但如果你只是返回数组中的Id的值,你可能不得不select这个语法。 因为我似乎无法得到indexOf值与mongo ObjectId格式相匹配。
obj.map = function() { for(var i = 0; i < inputs.length; i++){ if(this._id.equals(inputs[i])) { var order = i; } } emit(order, {doc: this}); };
如何转换mongo ObjectId .toString不包括'ObjectId()'包装 – 只是值?
我知道这个问题与Mongoose JS框架有关,但是重复的是generics的,所以我希望在这里发布一个Python(PyMongo)解决scheme。
things = list(db.things.find({'_id': {'$in': id_array}})) things.sort(key=lambda thing: id_array.index(thing['_id'])) # things are now sorted according to id_array order
您可以用$或子句保证订单。
所以使用$or: [ _ids.map(_id => ({_id}))]
来代替。
从Mongo检索结果之后,这是一个代码解决scheme。 使用映射来存储索引,然后交换值。
catDetails := make([]CategoryDetail, 0) err = sess.DB(mdb).C("category"). Find(bson.M{ "_id": bson.M{"$in": path}, "is_active": 1, "name": bson.M{"$ne": ""}, "url.path": bson.M{"$exists": true, "$ne": ""}, }). Select( bson.M{ "is_active": 1, "name": 1, "url.path": 1, }).All(&catDetails) if err != nil{ return } categoryOrderMap := make(map[int]int) for index, v := range catDetails { categoryOrderMap[v.Id] = index } counter := 0 for i := 0; counter < len(categoryOrderMap); i++ { if catId := int(path[i].(float64)); catId > 0 { fmt.Println("cat", catId) if swapIndex, exists := categoryOrderMap[catId]; exists { if counter != swapIndex { catDetails[swapIndex], catDetails[counter] = catDetails[counter], catDetails[swapIndex] categoryOrderMap[catId] = counter categoryOrderMap[catDetails[swapIndex].Id] = swapIndex } counter++ } } }
在mongo返回数组之后sorting结果的简单方法是创build一个id为key的对象,然后映射给定的_id以返回正确sorting的数组。
async function batchUsers(Users, keys) { const unorderedUsers = await Users.find({_id: {$in: keys}}).toArray() let obj = {} unorderedUsers.forEach(x => obj[x._id]=x) const ordered = keys.map(key => obj[key]) return ordered }