是否可能按照列表的顺序对Mongo查询进行排序?
在MongoDB中,使用$in子句的数组参数的顺序并不反映检索文档的顺序,而是按照自然顺序或选定的索引顺序进行检索。如果需要保留这个顺序,可以有两种选项。
第一种方法是使用聚合(aggregate)操作:
var list = [ 4, 2, 8 ]; db.collection.aggregate([ { "$match": { "_id": { "$in": [ 4, 2, 8 ] }, }, { "$project": { "weight": { "$cond": [ { "$eq": [ "$_id", 4 ] }, 1, { "$cond": [ { "$eq": [ "$_id", 2 ] }, 2, 3 ]} ]} }}, { "$sort": { "weight": 1 } } ])
第二种方法是使用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; } } )
以上就是在$in条件中保持输入列表顺序的两种方法。第一种方法使用聚合操作,通过构建一个嵌套的$cond语句来测试值并分配适当的权重,然后通过排序阶段将该权重值传递给结果以获取所需的顺序。第二种方法使用mapReduce操作,依赖于发射的键值在输入数组中按照索引顺序出现。
如果数组项是对象ID,则使用聚合方法的性能如何,这个问题没有给出明确的答案。
问题的出现原因是希望按照一个列表的顺序对Mongo查询结果进行排序,但是MongoDB不直接支持按照列表顺序排序的功能。为了解决这个问题,可以使用find方法查询结果后,在客户端使用JavaScript数组的sort方法进行排序。
如果$in的值是原始类型(如数字),可以使用以下方法:
var ids = [4, 2, 8, 1, 9, 3, 5, 6]; MyModel.find({ _id: { $in: ids } }).exec(function(err, docs) { docs.sort(function(a, b) { // 根据ids中_id的顺序对docs进行排序 return ids.indexOf(a._id) - ids.indexOf(b._id); }); });
如果$in的值是非原始类型(如ObjectId),则需要使用不同的方法,因为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版本中,可以使用underscore/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); }); });
其中,equals函数是Mongoose的Document#equals方法,用于比较文档的_id字段。
在MongoDB版本 >= 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);
上述查询中的聚合操作符的含义如下:
- `$match`:匹配name字段在order列表中的文档
- `$addFields`:添加一个名为`__order`的新字段,该字段的值为name在order列表中的索引位置
- `$sort`:根据`__order`字段的值进行升序排序
这样,我们就可以按照指定顺序对文档进行排序。
此外,可以通过将order数组存储为查询的变量来避免在查询中重复使用相同的数组。这样可以简化查询语句,特别是当数组很大时。具体实现方法可以参考以下代码:
var order = new[] { "David", "Charlie", "Tess" }; var query = new BsonDocument[] { new BsonDocument("$match", new BsonDocument("name", new BsonDocument("$in", new BsonArray(order)))), new BsonDocument("$addFields", new BsonDocument("__order", new BsonDocument("$indexOfArray", new BsonArray(new BsonValue[] { new BsonArray(order), "$name" })))), new BsonDocument("$sort", new BsonDocument("__order", 1)) }; var result = collection.Aggregate(query).ToList();
以上是将MongoDB中按照列表顺序排序查询结果的方法以及如何在C#的LINQ中实现的思路和代码。