Saat menggunakan $in
klausa MongoDB , apakah urutan dokumen yang dikembalikan selalu sesuai dengan urutan argumen array?
mongodb
mongoose
mapreduce
mongodb-query
aggregation-framework
pengguna2066880
sumber
sumber
Jawaban:
Sebagaimana dicatat, urutan argumen dalam larik klausa $ in tidak mencerminkan urutan bagaimana dokumen diambil. Itu tentu saja akan menjadi urutan alami atau dengan urutan indeks yang dipilih seperti yang ditunjukkan.
Jika Anda perlu mempertahankan tatanan ini, pada dasarnya Anda memiliki dua opsi.
Jadi katakanlah Anda mencocokkan nilai
_id
dalam dokumen Anda dengan array yang akan diteruskan ke$in
as[ 4, 2, 8 ]
.Pendekatan menggunakan Agregat
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 } } ])
Jadi itu akan menjadi bentuk yang diperluas. Apa yang pada dasarnya terjadi di sini adalah bahwa sama seperti larik nilai yang diteruskan kepada
$in
Anda, Anda juga membuat pernyataan "bersarang"$cond
untuk menguji nilai dan menetapkan bobot yang sesuai. Karena nilai "bobot" tersebut mencerminkan urutan elemen dalam larik, Anda kemudian dapat meneruskan nilai tersebut ke tahap pengurutan untuk mendapatkan hasil dalam urutan yang diperlukan.Tentu saja Anda benar-benar "membangun" pernyataan pipeline dalam kode, seperti ini:
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 );
Pendekatan menggunakan mapReduce
Tentu saja jika semua itu tampak lumayan untuk kepekaan Anda, maka Anda dapat melakukan hal yang sama menggunakan mapReduce, yang terlihat lebih sederhana tetapi kemungkinan akan berjalan agak lebih lambat.
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; } } )
Dan itu pada dasarnya bergantung pada nilai "kunci" yang dipancarkan berada dalam "urutan indeks" bagaimana mereka muncul dalam larik masukan.
Jadi pada dasarnya itu adalah cara Anda mempertahankan urutan daftar input ke
$in
kondisi di mana Anda sudah memiliki daftar itu dalam urutan yang ditentukan.sumber
Cara lain menggunakan kueri Agregasi hanya berlaku untuk MongoDB verion> = 3.4 -
Penghargaan untuk kebaikan ini entri blog yang .
Contoh dokumen yang akan diambil dalam pesanan ini -
var order = [ "David", "Charlie", "Tess" ];
Kueri -
var query = [ {$match: {name: {$in: order}}}, {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}}, {$sort: {"__order": 1}} ]; var result = db.users.aggregate(query);
Kutipan lain dari posting yang menjelaskan operator agregasi ini digunakan -
Pada dasarnya
addFields
operator menambahkanorder
bidang baru ke setiap dokumen ketika ditemukan danorder
bidang ini mewakili urutan asli dari larik yang kami sediakan. Kemudian kami cukup mengurutkan dokumen berdasarkan bidang ini.sumber
Jika Anda tidak ingin menggunakan
aggregate
, solusi lain adalah menggunakanfind
dan kemudian mengurutkan hasil dokumen sisi klien menggunakanarray#sort
:Jika
$in
nilainya adalah tipe primitif seperti angka, Anda dapat menggunakan pendekatan seperti: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); }); });
Jika
$in
nilainya adalah tipe non-primitif sepertiObjectId
s, pendekatan lain diperlukan sebagaiindexOf
perbandingan dengan referensi dalam kasus tersebut.Jika Anda menggunakan Node.js 4.x +, Anda dapat menggunakan
Array#findIndex
danObjectID#equals
menangani ini dengan mengubahsort
fungsinya menjadi:docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - ids.findIndex(id => b._id.equals(id)));
Atau dengan versi Node.js apa pun, dengan garis bawah / 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); }); });
sumber
Document#equals
untuk membandingkan dengan bidang dokumen_id
. Diperbarui untuk membuat_id
perbandingan menjadi eksplisit. Terima kasih untuk bertanya.Mirip dengan solusi JonnyHK , Anda dapat menyusun ulang dokumen yang dikembalikan dari
find
klien Anda (jika klien Anda menggunakan JavaScript) dengan kombinasimap
danArray.prototype.find
fungsi di 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); }); }); });
Beberapa catatan:
idArray
adalah larikObjectId
map
callback untuk menyederhanakan kode Anda.sumber
find
melintasi larik untuk setiap elemen larik (dari luarmap
). Ini sangat tidak efisien, karena ada solusi O (n) menggunakan tabel pencarian.Saya tahu pertanyaan ini terkait dengan kerangka JS Mongoose, tetapi yang digandakan bersifat umum, jadi saya harap memposting solusi Python (PyMongo) baik-baik saja di sini.
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
sumber
Cara mudah untuk mengurutkan hasil setelah mongo mengembalikan larik adalah dengan membuat objek dengan id sebagai kunci dan kemudian memetakan di atas _id yang diberikan untuk mengembalikan larik yang diurutkan dengan benar.
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 }
sumber
Selalu? Tidak pernah. Urutannya selalu sama: tidak ditentukan (mungkin urutan fisik tempat dokumen disimpan). Kecuali Anda mengurutkannya.
sumber
$natural
memesan secara normal yang lebih logis daripada fisikSaya tahu ini adalah utas lama, tetapi jika Anda baru saja mengembalikan nilai Id dalam larik, Anda mungkin harus memilih sintaks ini. Karena saya tidak bisa mendapatkan nilai indexOf untuk mencocokkan dengan format 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}); };
Bagaimana cara mengonversi ObjectId .toString mongo tanpa menyertakan pembungkus 'ObjectId ()' - hanya Nilai?
sumber
Anda dapat menjamin pesanan dengan $ atau klausul.
Jadi gunakan
$or: [ _ids.map(_id => ({_id}))]
saja.sumber
$or
solusi tidak bekerja sejak v2.6 .Ini adalah solusi kode setelah hasil diambil dari Mongo. Menggunakan peta untuk menyimpan indeks dan kemudian menukar nilai.
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++ } } }
sumber