Lors de l'utilisation de la $in
clause MongoDB , l'ordre des documents renvoyés correspond-il toujours à l'ordre de l'argument du tableau?
mongodb
mongoose
mapreduce
mongodb-query
aggregation-framework
user2066880
la source
la source
Réponses:
Comme indiqué, l'ordre des arguments dans le tableau d'une clause $ in ne reflète pas l'ordre dans lequel les documents sont récupérés. Ce sera bien sûr l'ordre naturel ou par l'ordre d'index sélectionné comme indiqué.
Si vous devez conserver cet ordre, vous avez essentiellement deux options.
Supposons donc que vous compariez les valeurs de
_id
dans vos documents avec un tableau qui va être transmis à$in
as[ 4, 2, 8 ]
.Approche utilisant Aggregate
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 } } ])
Ce serait donc le formulaire élargi. Ce qui se passe essentiellement ici, c'est que tout comme le tableau de valeurs vous est transmis,
$in
construisez également une$cond
instruction "imbriquée" pour tester les valeurs et attribuer un poids approprié. Comme cette valeur de «poids» reflète l'ordre des éléments dans le tableau, vous pouvez ensuite passer cette valeur à une étape de tri afin d'obtenir vos résultats dans l'ordre requis.Bien sûr, vous "construisez" l'instruction pipeline dans le code, un peu comme ceci:
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 );
Approche à l'aide de mapReduce
Bien sûr, si tout cela semble trop important pour votre sensibilité, vous pouvez faire la même chose en utilisant mapReduce, qui semble plus simple mais fonctionnera probablement un peu plus lentement.
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; } } )
Et cela repose essentiellement sur le fait que les valeurs «clés» émises sont dans «l'ordre d'index» de la façon dont elles se produisent dans le tableau d'entrée.
Donc, ce sont essentiellement vos façons de maintenir l'ordre d'une liste d'entrée à une
$in
condition où vous avez déjà cette liste dans un ordre déterminé.la source
Une autre façon d'utiliser la requête d'agrégation applicable uniquement pour la version MongoDB> = 3.4 -
Le mérite revient à ce joli article de blog .
Exemples de documents à récupérer dans cet ordre -
var order = [ "David", "Charlie", "Tess" ];
La requête -
var query = [ {$match: {name: {$in: order}}}, {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}}, {$sort: {"__order": 1}} ]; var result = db.users.aggregate(query);
Une autre citation du post expliquant ces opérateurs d'agrégation utilisés -
En gros, l'
addFields
opérateur ajoute un nouveauorder
champ à chaque document lorsqu'il le trouve et ceorder
champ représente l'ordre d'origine de notre tableau que nous avons fourni. Ensuite, nous trions simplement les documents en fonction de ce champ.la source
Si vous ne souhaitez pas utiliser
aggregate
, une autre solution consiste à utiliserfind
, puis à trier les résultats de la documentation côté client en utilisantarray#sort
:Si les
$in
valeurs sont des types primitifs comme les nombres, vous pouvez utiliser une approche comme: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); }); });
Si les
$in
valeurs sont des types non primitifs tels queObjectId
s, une autre approche est nécessaire pourindexOf
comparer par référence dans ce cas.Si vous utilisez Node.js 4.x +, vous pouvez utiliser
Array#findIndex
etObjectID#equals
gérer cela en modifiant lasort
fonction en:docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - ids.findIndex(id => b._id.equals(id)));
Ou avec n'importe quelle version de Node.js, avec des traits de soulignement / 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); }); });
la source
Document#equals
pour comparer avec le_id
champ du doc . Mis à jour pour rendre la_id
comparaison explicite. Merci d'avoir posé la question.Semblable à la solution de JonnyHK , vous pouvez réorganiser les documents renvoyés par
find
votre client (si votre client est en JavaScript) avec une combinaison demap
et laArray.prototype.find
fonction dans 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); }); }); });
Quelques notes:
idArray
est un tableau deObjectId
map
rappel pour simplifier votre code.la source
Un moyen simple de classer le résultat après que mongo a renvoyé le tableau consiste à créer un objet avec id comme clés, puis à mapper les _id donnés pour renvoyer un tableau correctement ordonné.
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 }
la source
Je sais que cette question est liée au framework Mongoose JS, mais la duplication est générique, donc j'espère que publier une solution Python (PyMongo) est bien ici.
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
la source
Toujours? Jamais. L'ordre est toujours le même: indéfini (probablement l'ordre physique dans lequel les documents sont stockés). Sauf si vous le triez.
la source
$natural
commande normalement, ce qui est logique plutôt que physiqueJe sais que c'est un ancien thread, mais si vous ne renvoyez que la valeur de l'Id dans le tableau, vous devrez peut-être opter pour cette syntaxe. Comme je ne pouvais pas sembler obtenir la valeur indexOf pour correspondre avec un format 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}); };
Comment convertir mongo ObjectId .toString sans inclure le wrapper 'ObjectId ()' - juste la valeur?
la source
Vous pouvez garantir la commande avec $ ou clause.
Alors utilisez
$or: [ _ids.map(_id => ({_id}))]
plutôt.la source
$or
solution de contournement n'a pas fonctionné depuis la v2.6 .Il s'agit d'une solution de code une fois les résultats récupérés à partir de Mongo. Utilisation d'une carte pour stocker l'index, puis permutation des valeurs.
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++ } } }
la source