Khi sử dụng $in
mệnh đề MongoDB , thứ tự của các tài liệu trả về có luôn tương ứng với thứ tự của đối số mảng không?
Khi sử dụng $in
mệnh đề MongoDB , thứ tự của các tài liệu trả về có luôn tương ứng với thứ tự của đối số mảng không?
Câu trả lời:
Như đã lưu ý, thứ tự của các đối số trong mảng của mệnh đề $ in không phản ánh thứ tự của cách tài liệu được truy xuất. Tất nhiên đó sẽ là thứ tự tự nhiên hoặc theo thứ tự chỉ mục đã chọn như hình.
Nếu bạn cần duy trì đơn đặt hàng này, thì về cơ bản bạn có hai lựa chọn.
Vì vậy, giả sử rằng bạn đang đối sánh các giá trị _id
trong tài liệu của mình với một mảng sẽ được chuyển vào $in
dưới dạng [ 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 } }
])
Vì vậy, đó sẽ là dạng mở rộng. Điều cơ bản xảy ra ở đây là cũng giống như mảng giá trị được chuyển cho $in
bạn cũng tạo một $cond
câu lệnh "lồng nhau" để kiểm tra các giá trị và gán trọng số thích hợp. Vì giá trị "trọng số" đó phản ánh thứ tự của các phần tử trong mảng, sau đó bạn có thể chuyển giá trị đó sang một giai đoạn sắp xếp để nhận được kết quả của bạn theo thứ tự bắt buộc.
Tất nhiên bạn thực sự "xây dựng" câu lệnh đường ống trong mã, giống như thế này:
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 );
Tất nhiên nếu tất cả điều đó dường như quá lớn đối với sự nhạy cảm của bạn thì bạn có thể làm điều tương tự bằng cách sử dụng mapReduce, trông đơn giản hơn nhưng có thể sẽ chạy chậm hơn một chút.
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;
}
}
)
Và điều đó về cơ bản dựa vào các giá trị "khóa" được phát ra nằm trong "thứ tự chỉ mục" về cách chúng xuất hiện trong mảng đầu vào.
Vì vậy, đó về cơ bản là những cách bạn duy trì thứ tự của một danh sách đầu vào với một $in
điều kiện mà bạn đã có danh sách đó theo một thứ tự xác định.
Một cách khác sử dụng truy vấn Tổng hợp chỉ áp dụng cho xác minh MongoDB> = 3.4 -
Tín dụng dành cho bài đăng blog tốt đẹp này .
Các tài liệu mẫu sẽ được tìm nạp theo thứ tự này -
var order = [ "David", "Charlie", "Tess" ];
Truy vấn -
var query = [
{$match: {name: {$in: order}}},
{$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
{$sort: {"__order": 1}}
];
var result = db.users.aggregate(query);
Một trích dẫn khác từ bài đăng giải thích các toán tử tổng hợp này được sử dụng -
Giai đoạn "$ addFields" là mới trong 3.4 và nó cho phép bạn "$ chiếu" các trường mới vào tài liệu hiện có mà không cần biết tất cả các trường hiện có khác. Biểu thức "$ indexOfArray" mới trả về vị trí của phần tử cụ thể trong một mảng nhất định.
Về cơ bản, addFields
toán tử thêm một order
trường mới vào mọi tài liệu khi nó tìm thấy nó và order
trường này đại diện cho thứ tự ban đầu của mảng mà chúng tôi đã cung cấp. Sau đó, chúng tôi chỉ cần sắp xếp các tài liệu dựa trên trường này.
Nếu bạn không muốn sử dụng aggregate
, một giải pháp khác là sử dụng find
và sau đó sắp xếp kết quả tài liệu phía máy khách bằng cách sử dụng array#sort
:
Nếu các $in
giá trị là kiểu nguyên thủy như số, bạn có thể sử dụng cách tiếp cận như:
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);
});
});
Nếu các $in
giá trị là kiểu không nguyên thủy như ObjectId
s, thì cần phải có một cách tiếp cận khác để indexOf
so sánh bằng tham chiếu trong trường hợp đó.
Nếu bạn đang sử dụng Node.js 4.x +, bạn có thể sử dụng Array#findIndex
và ObjectID#equals
xử lý điều này bằng cách thay đổi sort
chức năng thành:
docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) -
ids.findIndex(id => b._id.equals(id)));
Hoặc với bất kỳ phiên bản Node.js nào, với dấu gạch dưới / 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); });
});
Document#equals
để so sánh với trường của doc _id
. Đã cập nhật để làm cho _id
so sánh rõ ràng. Cam ơn vi đa hỏi.
Tương tự như giải pháp của JonnyHK , bạn có thể sắp xếp lại thứ tự các tài liệu được trả về từ find
máy khách của mình (nếu máy khách của bạn sử dụng JavaScript) với sự kết hợp của map
và Array.prototype.find
hàm trong EcmaScript 2015:
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);
});
});
});
Một số lưu ý:
idArray
một mảng củaObjectId
map
gọi lại để đơn giản hóa mã của mình.Một cách dễ dàng để sắp xếp kết quả sau khi mongo trả về mảng là tạo một đối tượng có id là các khóa và sau đó ánh xạ lên các _id đã cho để trả về một mảng được sắp xếp chính xác.
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
}
Tôi biết câu hỏi này liên quan đến khuôn khổ Mongoose JS, nhưng câu hỏi trùng lặp là chung chung, vì vậy tôi hy vọng đăng giải pháp Python (PyMongo) ở đây là tốt.
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
Tôi biết đây là một chuỗi cũ, nhưng nếu bạn chỉ trả về giá trị của Id trong mảng, bạn có thể phải chọn cú pháp này. Vì dường như tôi không thể nhận được giá trị indexOf để khớp với định dạng ObjectId mongo.
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
Bạn có thể đảm bảo đơn đặt hàng với $ hoặc điều khoản.
Vì vậy, hãy sử dụng $or: [ _ids.map(_id => ({_id}))]
thay thế.
$or
pháp thay thế đã không hoạt động kể từ v2.6 .
Đây là một giải pháp mã sau khi kết quả được truy xuất từ Mongo. Sử dụng bản đồ để lưu trữ chỉ mục và sau đó hoán đổi các giá trị.
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++
}
}
}