เมื่อใช้$in
ประโยคของ MongoDB ลำดับของเอกสารที่ส่งคืนจะสอดคล้องกับลำดับของอาร์กิวเมนต์อาร์เรย์หรือไม่
เมื่อใช้$in
ประโยคของ MongoDB ลำดับของเอกสารที่ส่งคืนจะสอดคล้องกับลำดับของอาร์กิวเมนต์อาร์เรย์หรือไม่
คำตอบ:
ตามที่ระบุไว้ลำดับของอาร์กิวเมนต์ในอาร์เรย์ของ $ ในอนุประโยคไม่ได้แสดงถึงลำดับวิธีการดึงเอกสาร แน่นอนว่าจะเป็นลำดับธรรมชาติหรือตามลำดับดัชนีที่เลือกดังที่แสดง
หากคุณต้องการรักษาคำสั่งซื้อนี้โดยพื้นฐานแล้วคุณมีสองตัวเลือก
ดังนั้นขอบอกว่าคุณถูกจับคู่กับค่าของ_id
ในเอกสารของคุณด้วยอาร์เรย์ที่กำลังจะถูกส่งผ่านไปยังเป็น$in
[ 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 } }
])
นั่นจะเป็นรูปแบบขยาย สิ่งที่เกิดขึ้นโดยทั่วไปที่นี่ก็คือเมื่ออาร์เรย์ของค่าถูกส่งไป$in
ยังคุณก็สร้าง "ซ้อน"$cond
คำสั่งเพื่อทดสอบค่าและกำหนดน้ำหนักที่เหมาะสม เนื่องจากค่า "น้ำหนัก" นั้นสะท้อนถึงลำดับขององค์ประกอบในอาร์เรย์คุณจึงสามารถส่งผ่านค่านั้นไปยังขั้นการจัดเรียงเพื่อให้ได้ผลลัพธ์ตามลำดับที่ต้องการ
แน่นอนคุณ "สร้าง" คำสั่งไปป์ไลน์ในโค้ดเช่นนี้:
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 ซึ่งดูง่ายกว่า แต่มีแนวโน้มว่าจะทำงานช้าลงบ้าง
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
เงื่อนไขที่คุณมีรายการนั้นตามลำดับที่กำหนด
อีกวิธีหนึ่งในการใช้แบบสอบถาม 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);
คำพูดอื่นจากโพสต์ที่อธิบายตัวดำเนินการรวมเหล่านี้ที่ใช้ -
ขั้นตอน "$ addFields" เป็นขั้นตอนใหม่ใน 3.4 และช่วยให้คุณสามารถ "$ project" ฟิลด์ใหม่ไปยังเอกสารที่มีอยู่โดยไม่ต้องรู้ฟิลด์อื่น ๆ ที่มีอยู่ทั้งหมด นิพจน์ "$ indexOfArray" ใหม่จะส่งคืนตำแหน่งขององค์ประกอบเฉพาะในอาร์เรย์ที่กำหนด
โดยทั่วไปตัวaddFields
ดำเนินการจะต่อท้ายorder
ฟิลด์ใหม่ในทุกเอกสารเมื่อพบและorder
ฟิลด์นี้แสดงถึงลำดับเดิมของอาร์เรย์ของเราที่เราให้ไว้ จากนั้นเราก็จัดเรียงเอกสารตามฟิลด์นี้
หากคุณไม่ต้องการใช้aggregate
วิธีแก้ปัญหาอื่นคือใช้find
แล้วเรียงลำดับ doc ผลลัพธ์ฝั่งไคลเอ็นต์โดยใช้array#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) {
// Sort docs by the order of their _id values in ids.
return ids.indexOf(a._id) - ids.indexOf(b._id);
});
});
หาก$in
ค่าเป็นชนิดที่ไม่ใช่พื้นฐานเช่นObjectId
s จำเป็นต้องใช้วิธีอื่นเพื่อ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); });
});
Document#equals
เพื่อเปรียบเทียบกับ_id
ฟิลด์ของหมอ อัปเดตเพื่อให้การ_id
เปรียบเทียบมีความชัดเจน ขอบคุณที่ถาม.
เช่นเดียวกับโซลูชันของJonnyHKคุณสามารถจัดลำดับเอกสารที่ส่งคืนจากfind
ในไคลเอนต์ของคุณใหม่ได้ (หากไคลเอนต์ของคุณอยู่ใน JavaScript) ด้วยการรวมกันของmap
และArray.prototype.find
ฟังก์ชันใน 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);
});
});
});
หมายเหตุสองสามข้อ:
idArray
เป็นอาร์เรย์ของObjectId
map
ติดต่อกลับเพื่อลดความซับซ้อนของรหัสfind
ข้ามอาร์เรย์สำหรับแต่ละองค์ประกอบของอาร์เรย์ (จากด้านนอกmap
) สิ่งนี้ไม่มีประสิทธิภาพอย่างมากเนื่องจากมี O (n) วิธีแก้ปัญหาโดยใช้ตารางการค้นหา
ฉันรู้ว่าคำถามนี้เกี่ยวข้องกับเฟรมเวิร์ก Mongoose JS แต่คำถามที่ซ้ำกันนั้นเป็นแบบทั่วไปดังนั้นฉันหวังว่าการโพสต์โซลูชัน Python (PyMongo) จะดีที่นี่
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
วิธีง่ายๆในการเรียงลำดับผลลัพธ์หลังจากที่ mongo ส่งคืนอาร์เรย์คือการสร้างอ็อบเจกต์ที่มี id เป็นคีย์จากนั้นแมปบน _id ที่กำหนดเพื่อส่งคืนอาร์เรย์ที่เรียงลำดับอย่างถูกต้อง
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
}
เสมอ? ไม่เลย ลำดับจะเหมือนกันเสมอ: ไม่ได้กำหนด (อาจเป็นลำดับทางกายภาพที่จัดเก็บเอกสาร) เว้นแต่คุณจะจัดเรียง
$natural
สั่งซื้อตามปกติซึ่งเป็นตรรกะมากกว่าทางกายภาพ
ฉันรู้ว่านี่เป็นเธรดเก่า แต่ถ้าคุณเพิ่งคืนค่าของ Id ในอาร์เรย์คุณอาจต้องเลือกใช้ไวยากรณ์นี้ เนื่องจากฉันไม่สามารถรับค่า 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 ()' - เพียงแค่ค่า?
คุณสามารถรับประกันการสั่งซื้อด้วย $ หรือ clause
เลยใช้$or: [ _ids.map(_id => ({_id}))]
แทน.
นี่คือวิธีแก้รหัสหลังจากที่ดึงผลลัพธ์จาก Mongo การใช้แผนที่เพื่อจัดเก็บดัชนีแล้วแลกเปลี่ยนค่า
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++
}
}
}