MongoDBの$in
句を使用する場合、返されるドキュメントの順序は常に配列引数の順序に対応していますか?
回答:
前述のように、$ in句の配列内の引数の順序は、ドキュメントの取得方法の順序を反映していません。もちろん、それは自然な順序、または示されているように選択されたインデックスの順序になります。
この順序を維持する必要がある場合は、基本的に2つのオプションがあります。
したがって_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
そのリストが決定された順序で既に存在する状態に維持する方法です。
のみ適用集計クエリ使用して別の方法の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
から、クライアント側で次を使用してドキュメント結果を並べ替えることです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
のように、別のアプローチが必要とされ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のソリューションと同様に、EcmaScript 2015の関数とのfind
組み合わせを使用して、クライアントから返されたドキュメントを並べ替えることができます(クライアントがJavaScriptの場合)。map
Array.prototype.find
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)ソリューションがあるため、これはひどく非効率的です。
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値をmongoObjectId形式と一致させることができなかったようです。
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
'ObjectId()'ラッパーを含めずにmongo ObjectId .toStringを変換する方法-値だけですか?
$ or句を使用して注文を保証できます。
したがって、$or: [ _ids.map(_id => ({_id}))]
代わりに使用してください。
$or
この問題を回避するには、働いていないV2.6以降。
これは、結果が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++
}
}
}