MongoDB'nin $in
cümlesini kullanırken , döndürülen belgelerin sırası her zaman dizi bağımsız değişkeninin sırasına karşılık gelir mi?
MongoDB'nin $in
cümlesini kullanırken , döndürülen belgelerin sırası her zaman dizi bağımsız değişkeninin sırasına karşılık gelir mi?
Yanıtlar:
Belirtildiği gibi, bir $ in yan tümcesi dizisindeki bağımsız değişkenlerin sırası, belgelerin alınma sırasını yansıtmaz. Bu, tabii ki doğal sıra veya gösterildiği gibi seçilen endeks sırasına göre olacaktır.
Bu sırayı korumanız gerekiyorsa, temelde iki seçeneğiniz vardır.
O halde sen değerlerine uyan edildi diyelim _id
geçirilen olacak bir dizi ile belgelerde $in
olduğu gibi [ 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 } }
])
Böylece genişletilmiş form bu olacaktır. Burada temelde olan şey, tıpkı değerler dizisinin $in
size aktarılması gibi, değerleri $cond
test etmek ve uygun bir ağırlık atamak için "iç içe geçmiş" bir ifade oluşturmasıdır. Bu "ağırlık" değeri dizideki öğelerin sırasını yansıttığından, sonuçlarınızı gereken sırada almak için bu değeri bir sıralama aşamasına geçirebilirsiniz.
Tabii ki aslında ardışık düzen ifadesini kodda "oluşturursunuz", buna çok benzer:
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 );
Tabii ki tüm bunlar duyarlılıklarınız için ağır görünüyorsa, aynı şeyi mapReduce kullanarak da yapabilirsiniz, bu daha basit görünen ancak muhtemelen biraz daha yavaş çalışacaktır.
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;
}
}
)
Ve bu temelde, emilen "anahtar" değerlerinin girdi dizisinde nasıl meydana geldiklerine ilişkin "dizin sırası" nda olmasına dayanır.
Dolayısıyla bunlar, esasen, bir giriş listesinin sırasını, $in
bu listeyi belirli bir sırayla zaten bulundurduğunuz bir koşulda sürdürme yollarınızdır .
Yalnızca MongoDB sürümü> = 3.4 için geçerli olan Toplama sorgusunu kullanmanın başka bir yolu -
Kredi bu güzel blog gönderisine gidiyor .
Bu siparişte getirilecek örnek belgeler -
var order = [ "David", "Charlie", "Tess" ];
Sorgu -
var query = [
{$match: {name: {$in: order}}},
{$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
{$sort: {"__order": 1}}
];
var result = db.users.aggregate(query);
Gönderiden kullanılan bu toplama operatörlerini açıklayan başka bir alıntı -
"$ AddFields" aşaması 3.4 sürümünde yenidir ve mevcut tüm diğer alanları bilmeden mevcut belgelere yeni alanlar "$ projelendirmenize" olanak tanır. Yeni "$ indexOfArray" ifadesi, belirli bir dizideki belirli öğenin konumunu döndürür.
Temel olarak addFields
operatör order
, onu bulduğunda her belgeye yeni bir alan ekler ve bu order
alan, sağladığımız dizimizin orijinal sırasını temsil eder. Sonra belgeleri bu alana göre sıralarız.
Kullanmak istemiyorsanız aggregate
, başka bir çözüm kullanmak find
ve ardından belge sonuçlarını istemci tarafında sıralamaktır.array#sort
:
Eğer $in
değerler numaraları gibi ilkel türleri aşağıdaki gibi bir yaklaşımı kullanabilirsiniz:
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);
});
});
Eğer $in
değerler temel olmayan türleri gibiObjectId
s gibi başka yaklaşım gereklidir indexOf
, bu durumda, referans ile karşılaştırır.
Node.js 4.x + kullanıyorsanız , işlevini şu şekilde değiştirerek bunu kullanabilir Array#findIndex
ve ObjectID#equals
işleyebilirsiniz sort
:
docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) -
ids.findIndex(id => b._id.equals(id)));
Veya alt çizgi / lodash ile herhangi bir Node.js sürümünde 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
doktorun _id
alanıyla karşılaştırmak için Mongoose'u kullanıyordu . _id
Karşılaştırmayı açık hale getirmek için güncellendi . Sorduğunuz için teşekkürler.
JonnyHK'nin çözümüne benzer şekilde find
, istemcinizden (istemciniz JavaScript'teyse) döndürülen belgeleri , EcmaScript 2015'teki map
ve Array.prototype.find
işlevinin bir kombinasyonu ile yeniden sıralayabilirsiniz :
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);
});
});
});
Birkaç not:
idArray
Bir dizidirObjectId
map
, kodunuzu basitleştirmek için geri aramada bunu yapabilirsiniz .find
dizinin her bir öğesi için diziyi (dıştan map
) geçtiği için çalışma süresi O (n * n ) 'dir. Bir arama tablosu kullanan bir O (n) çözümü olduğu için bu korkunç derecede verimsizdir.
Bu sorunun Mongoose JS çerçevesi ile ilgili olduğunu biliyorum, ancak çoğaltılmış olan geneldir, bu yüzden burada bir Python (PyMongo) çözümü göndermenin iyi olacağını umuyorum.
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 diziyi döndürdükten sonra sonucu sıralamanın kolay bir yolu, anahtar olarak id'li bir nesne yapmak ve ardından doğru sıralı bir dizi döndürmek için verilen _id'lerin üzerine eşlemektir.
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
}
Bunun eski bir iş parçacığı olduğunu biliyorum, ancak dizideki Id'nin değerini döndürüyorsanız, bu sözdizimini seçmeniz gerekebilir. Bir mongo ObjectId formatıyla eşleşecek indexOf değerini alamadığım için.
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
Siparişi $ veya fıkra ile garanti edebilirsiniz.
Onun $or: [ _ids.map(_id => ({_id}))]
yerine kullanın.
Bu, sonuçlar Mongo'dan alındıktan sonraki bir kod çözümüdür. Dizini depolamak için bir harita kullanmak ve ardından değerleri değiştirmek.
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++
}
}
}