モダン
MongoDB 3.6から、$lookup
を使用することによるこれへの「新しい」アプローチがあります。 以下に示す元のカーソル処理とほぼ同じ方法で「自己結合」を実行します。
このリリースでは、"pipeline"
を指定できるため $lookup
への引数 「参加」のソースとして、これは基本的に$match
を使用できることを意味します および$limit
アレイのエントリを収集して「制限」するには:
db.messages.aggregate([
{ "$group": { "_id": "$conversation_ID" } },
{ "$lookup": {
"from": "messages",
"let": { "conversation": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$conversation_ID", "$$conversation" ] } }},
{ "$limit": 10 },
{ "$project": { "_id": 1 } }
],
"as": "msgs"
}}
])
オプションで、$lookup
の後に投影を追加できます。 配列アイテムを_id
のドキュメントではなく、単に値にするため キーですが、基本的な結果は上記を実行するだけです。
実際に「プッシュの制限」を直接要求する未処理のSERVER-9277がまだありますが、$lookup
このように、暫定的に実行可能な代替手段です。
注 :
$slice
もあります これは、元の回答を書いた後に導入され、元のコンテンツの「未解決のJIRA問題」で言及されています。小さな結果セットでも同じ結果を得ることができますが、それでも配列に「すべてをプッシュ」し、後で最終的な配列出力を目的の長さに制限する必要があります。これが主な違いであり、
$slice
を実行することが一般的に実用的でない理由です。 大きな結果を得るには。ただし、もちろん、その場合は交互に使用することもできます。いずれかの代替使用法について、複数のフィールドによるmongodbグループ値の詳細がいくつかあります。
オリジナル
先に述べたように、これは不可能ではありませんが、確かに恐ろしい問題です。
実際、結果の配列が非常に大きくなることが主な懸念事項である場合、最善のアプローチは、個別のクエリとして個別の「conversation_ID」ごとに送信してから、結果を結合することです。非常にMongoDB2.6の構文では、言語の実装が実際に何であるかに応じて、微調整が必要になる場合があります。
var results = [];
db.messages.aggregate([
{ "$group": {
"_id": "$conversation_ID"
}}
]).forEach(function(doc) {
db.messages.aggregate([
{ "$match": { "conversation_ID": doc._id } },
{ "$limit": 10 },
{ "$group": {
"_id": "$conversation_ID",
"msgs": { "$push": "$_id" }
}}
]).forEach(function(res) {
results.push( res );
});
});
しかし、それはすべてあなたが避けようとしていることであるかどうかに依存します。本当の答えに移りましょう:
ここでの最初の問題は、配列に「プッシュ」されるアイテムの数を「制限」する機能がないことです。それは確かに私たちが望んでいることですが、機能は現在存在していません。
2番目の問題は、すべてのアイテムを配列にプッシュする場合でも、 $slice
を使用できないことです。 、または集計パイプライン内の同様の演算子。したがって、簡単な操作で生成されたアレイから「トップ10」の結果だけを取得する現在の方法はありません。
ただし、実際には、グループ化の境界を効果的に「スライス」するための一連の操作を作成できます。これはかなり複雑です。たとえば、ここでは、「スライス」された配列要素を「6」のみに減らします。ここでの主な理由は、プロセスを示し、「スライス」する合計が含まれていないアレイを破壊せずにこれを行う方法を示すことです。
ドキュメントのサンプルを考えると:
{ "_id" : 1, "conversation_ID" : 123 }
{ "_id" : 2, "conversation_ID" : 123 }
{ "_id" : 3, "conversation_ID" : 123 }
{ "_id" : 4, "conversation_ID" : 123 }
{ "_id" : 5, "conversation_ID" : 123 }
{ "_id" : 6, "conversation_ID" : 123 }
{ "_id" : 7, "conversation_ID" : 123 }
{ "_id" : 8, "conversation_ID" : 123 }
{ "_id" : 9, "conversation_ID" : 123 }
{ "_id" : 10, "conversation_ID" : 123 }
{ "_id" : 11, "conversation_ID" : 123 }
{ "_id" : 12, "conversation_ID" : 456 }
{ "_id" : 13, "conversation_ID" : 456 }
{ "_id" : 14, "conversation_ID" : 456 }
{ "_id" : 15, "conversation_ID" : 456 }
{ "_id" : 16, "conversation_ID" : 456 }
条件でグループ化すると、1つの配列に10個の要素が含まれ、別の配列に「5個」の要素が含まれることがわかります。ここで実行したいことは、「5つの」要素にのみ一致する配列を「破壊」することなく、両方を上位の「6」に減らします。
そして次のクエリ:
db.messages.aggregate([
{ "$group": {
"_id": "$conversation_ID",
"first": { "$first": "$_id" },
"msgs": { "$push": "$_id" },
}},
{ "$unwind": "$msgs" },
{ "$project": {
"msgs": 1,
"first": 1,
"seen": { "$eq": [ "$first", "$msgs" ] }
}},
{ "$sort": { "seen": 1 }},
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
},
"first": { "$first": "$first" },
"second": { "$first": "$msgs" }
}},
{ "$unwind": "$msgs" },
{ "$project": {
"msgs": 1,
"first": 1,
"second": 1,
"seen": { "$eq": [ "$second", "$msgs" ] }
}},
{ "$sort": { "seen": 1 }},
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
},
"first": { "$first": "$first" },
"second": { "$first": "$second" },
"third": { "$first": "$msgs" }
}},
{ "$unwind": "$msgs" },
{ "$project": {
"msgs": 1,
"first": 1,
"second": 1,
"third": 1,
"seen": { "$eq": [ "$third", "$msgs" ] },
}},
{ "$sort": { "seen": 1 }},
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
},
"first": { "$first": "$first" },
"second": { "$first": "$second" },
"third": { "$first": "$third" },
"forth": { "$first": "$msgs" }
}},
{ "$unwind": "$msgs" },
{ "$project": {
"msgs": 1,
"first": 1,
"second": 1,
"third": 1,
"forth": 1,
"seen": { "$eq": [ "$forth", "$msgs" ] }
}},
{ "$sort": { "seen": 1 }},
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
},
"first": { "$first": "$first" },
"second": { "$first": "$second" },
"third": { "$first": "$third" },
"forth": { "$first": "$forth" },
"fifth": { "$first": "$msgs" }
}},
{ "$unwind": "$msgs" },
{ "$project": {
"msgs": 1,
"first": 1,
"second": 1,
"third": 1,
"forth": 1,
"fifth": 1,
"seen": { "$eq": [ "$fifth", "$msgs" ] }
}},
{ "$sort": { "seen": 1 }},
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
},
"first": { "$first": "$first" },
"second": { "$first": "$second" },
"third": { "$first": "$third" },
"forth": { "$first": "$forth" },
"fifth": { "$first": "$fifth" },
"sixth": { "$first": "$msgs" },
}},
{ "$project": {
"first": 1,
"second": 1,
"third": 1,
"forth": 1,
"fifth": 1,
"sixth": 1,
"pos": { "$const": [ 1,2,3,4,5,6 ] }
}},
{ "$unwind": "$pos" },
{ "$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [
{ "$eq": [ "$pos", 1 ] },
"$first",
{ "$cond": [
{ "$eq": [ "$pos", 2 ] },
"$second",
{ "$cond": [
{ "$eq": [ "$pos", 3 ] },
"$third",
{ "$cond": [
{ "$eq": [ "$pos", 4 ] },
"$forth",
{ "$cond": [
{ "$eq": [ "$pos", 5 ] },
"$fifth",
{ "$cond": [
{ "$eq": [ "$pos", 6 ] },
"$sixth",
false
]}
]}
]}
]}
]}
]
}
}
}},
{ "$unwind": "$msgs" },
{ "$match": { "msgs": { "$ne": false } }},
{ "$group": {
"_id": "$_id",
"msgs": { "$push": "$msgs" }
}}
])
最大6つのエントリで、配列の上位の結果を取得します。
{ "_id" : 123, "msgs" : [ 1, 2, 3, 4, 5, 6 ] }
{ "_id" : 456, "msgs" : [ 12, 13, 14, 15 ] }
ご覧のとおり、たくさんの楽しみがあります。
最初にグループ化した後、基本的に $first
を「ポップ」します。 配列結果のスタックからの値。このプロセスを少し単純化するために、実際には最初の操作でこれを行います。したがって、プロセスは次のようになります。
-
$unwind
アレイ -
$eq
ですでに見られた値と比較してください 平等一致 -
$sort
結果を「フロート」false
目に見えない値が一番上に表示されます(これでも順序は保持されます) -
$group
もう一度戻って、$first
を「ポップ」します スタックの次のメンバーとしての見えない値。また、これは$cond
を使用します 配列スタックの「表示された」値をfalse
に置き換える演算子 評価に役立てるため。
$cond
を使用した最後のアクション 将来の反復で、「スライス」カウントが配列メンバーよりも多い場合に、配列の最後の値を何度も追加するだけではないことを確認するためにあります。
そのプロセス全体を、「スライス」したい数のアイテムに対して繰り返す必要があります。最初のグループ化で「最初の」アイテムがすでに見つかっているため、これはn-1
を意味します。 目的のスライス結果の反復。
最後のステップは、実際には、最終的に示されている結果を得るために、すべてを配列に変換するオプションの図にすぎません。つまり、実際には条件付きでアイテムをプッシュするか、false
一致する位置に戻り、最後にすべてのfalse
を「フィルタリング」します 値なので、最後の配列にはそれぞれ「6」と「5」のメンバーがあります。
したがって、これに対応する標準の演算子はなく、プッシュを5または10、あるいは配列内の任意の項目に「制限」することはできません。しかし、本当にそれをしなければならないのであれば、これが最善のアプローチです。
mapReduceを使用してこれにアプローチし、集約フレームワークをすべて一緒に放棄することができます。私が(妥当な制限内で)採用するアプローチは、サーバー上にメモリ内のハッシュマップを効果的に配置し、それに配列を蓄積する一方で、JavaScriptスライスを使用して結果を「制限」することです。
db.messages.mapReduce(
function () {
if ( !stash.hasOwnProperty(this.conversation_ID) ) {
stash[this.conversation_ID] = [];
}
if ( stash[this.conversation_ID.length < maxLen ) {
stash[this.conversation_ID].push( this._id );
emit( this.conversation_ID, 1 );
}
},
function(key,values) {
return 1; // really just want to keep the keys
},
{
"scope": { "stash": {}, "maxLen": 10 },
"finalize": function(key,value) {
return { "msgs": stash[key] };
},
"out": { "inline": 1 }
}
)
つまり、基本的には、出力された「キー」と一致する「メモリ内」オブジェクトを、結果からフェッチする最大サイズを超えない配列で構築します。さらに、これは、最大スタックに達したときにアイテムを「放出」することさえしません。
削減部分は、実際には、基本的に「キー」と単一の値に削減する以外に何もしません。したがって、キーに値が1つしかない場合のように、レデューサーが呼び出されなかった場合に備えて、finalize関数が「stash」キーの最終出力へのマッピングを処理します。
これの効果は出力のサイズによって異なり、JavaScriptの評価は確かに高速ではありませんが、パイプラインで大きな配列を処理するよりも高速である可能性があります。
JIRAの問題に投票して、実際に「スライス」演算子、または「$push」と「$addToSet」に「制限」を設定します。どちらも便利です。 $map
に少なくともいくつかの変更を加えることができることを個人的に望んでいます 処理時に「現在のインデックス」値を公開する演算子。これにより、「スライス」やその他の操作が効果的に可能になります。
実際には、これをコーディングして、必要なすべての反復を「生成」する必要があります。ここでの答えが十分な愛情を持っている場合、および/または私が学んだことを保留している場合は、これを行う方法を示すためにいくつかのコードを追加する可能性があります。すでにかなり長い応答です。
パイプラインを生成するコード:
var key = "$conversation_ID";
var val = "$_id";
var maxLen = 10;
var stack = [];
var pipe = [];
var fproj = { "$project": { "pos": { "$const": [] } } };
for ( var x = 1; x <= maxLen; x++ ) {
fproj["$project"][""+x] = 1;
fproj["$project"]["pos"]["$const"].push( x );
var rec = {
"$cond": [ { "$eq": [ "$pos", x ] }, "$"+x ]
};
if ( stack.length == 0 ) {
rec["$cond"].push( false );
} else {
lval = stack.pop();
rec["$cond"].push( lval );
}
stack.push( rec );
if ( x == 1) {
pipe.push({ "$group": {
"_id": key,
"1": { "$first": val },
"msgs": { "$push": val }
}});
} else {
pipe.push({ "$unwind": "$msgs" });
var proj = {
"$project": {
"msgs": 1
}
};
proj["$project"]["seen"] = { "$eq": [ "$"+(x-1), "$msgs" ] };
var grp = {
"$group": {
"_id": "$_id",
"msgs": {
"$push": {
"$cond": [ { "$not": "$seen" }, "$msgs", false ]
}
}
}
};
for ( n=x; n >= 1; n-- ) {
if ( n != x )
proj["$project"][""+n] = 1;
grp["$group"][""+n] = ( n == x ) ? { "$first": "$msgs" } : { "$first": "$"+n };
}
pipe.push( proj );
pipe.push({ "$sort": { "seen": 1 } });
pipe.push(grp);
}
}
pipe.push(fproj);
pipe.push({ "$unwind": "$pos" });
pipe.push({
"$group": {
"_id": "$_id",
"msgs": { "$push": stack[0] }
}
});
pipe.push({ "$unwind": "$msgs" });
pipe.push({ "$match": { "msgs": { "$ne": false } }});
pipe.push({
"$group": {
"_id": "$_id",
"msgs": { "$push": "$msgs" }
}
});
これにより、maxLen
までの基本的な反復アプローチが構築されます。 $unwind
の手順で $group
へ 。また、必要な最終的な予測と「ネストされた」条件文の詳細も埋め込まれています。最後は基本的にこの質問に対するアプローチです:
MongoDBの$in句は順序を保証しますか?