$accumulator(集計)
定義
$accumulator
カスタム アキュムレータ演算子を定義します。アキュムレーターは、ドキュメントがパイプラインを通って処理される際に、ドキュメントの状態(たとえば、合計、最大、最小、および関連データ)を維持する演算子です。MongoDB のクエリ言語でサポートされていない動作を実装するには、
$accumulator
演算子を使用して独自の JavaScript 関数を実行します。$function
も参照してください。$accumulator
は、次のステージで使用できます。重要
集計演算子内で JavaScript を実行すると、パフォーマンスが低下する可能性があります。
$accumulator
提供された パイプライン演算子 が アプリケーションのニーズを満たせない場合にのみ、 演算子を使用してください。
構文
$accumulator
演算子の構文は次のとおりです。
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
フィールド | タイプ | 説明 | ||||
---|---|---|---|---|---|---|
文字列またはコード | 状態を初期化するために使用される関数。
| |||||
配列 | オプション。
重要 | |||||
文字列またはコード | ドキュメントを累積するために使用される関数。
| |||||
配列 |
| |||||
文字列またはコード | 2 つの内部状態をマージするために使用される関数。
| |||||
文字列またはコード | オプション。アキュムレーションの結果を更新するために使用される関数。
| |||||
文字列 |
重要:現在、 |
動作
次の手順は、 $accumulator
演算子がドキュメントを処理する方法の概要です。
演算子は、init 関数によって定義された初期状態から開始されます。
各ドキュメントについて、 演算子はaccumulate関数に基づいて状態をアップデートします。 accumulate関数の最初の引数は現在の状態で、追加の引数はaccumulateArgs配列で指定します。
演算子が複数の中間状態をマージする必要がある場合、マージ関数を実行します。 マージ関数が呼び出されるタイミングの詳細については、「
$merge
を使用して 2 つの状態をマージする 」を参照してください。finalize 関数が定義されている場合、すべてのドキュメントが処理され、それに応じて状態がアップデートされると、finalize は状態を最終出力に変換します。
2 つの状態をマージ $merge
$accumulator
演算子は、内部操作の一部として、2 つの個別の中間状態をマージする必要がある場合があります。 merge関数は、演算子が 2 つの状態をマージする方法を指定します。
merge 関数は常に、2 つの状態を一度にマージします。2 つ以上の状態をマージする必要がある場合、2 つの状態がマージされたものが 1 つの状態にマージされます。すべての状態がマージされるまで、このプロセスが繰り返されます。
たとえば、次のシナリオでは$accumulator
は 2 つの状態を組み合わせる必要がある場合があります。
$accumulator
はシャーディングされたクラスターで実行されます。演算子は、最終結果を得るために各シャードからの結果をマージする必要があります。単一の
$accumulator
操作が指定されたメモリ制限を超えています。allowDiskUse
オプションを指定すると、 演算子は進行中の操作をディスクに保存し、メモリ内で操作を終了します。 操作が完了すると、ディスクとメモリの結果が マージ関数を使用してまとめられます。
ドキュメント処理順序
MongoDB がinit()
、 accumulate()
、 merge()
関数のドキュメントを処理する順序はさまざまであり、それらのドキュメントが$accumulator
関数に指定される順序と異なる場合があります。
たとえば、 _id
フィールドがアルファベットの文字である一連のドキュメントを考えてみましょう。
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
次に、ドキュメントを_id
フィールドでソートし、 $accumulator
関数を使用して_id
フィールド値を連結する集計パイプラインを考えてみましょう。
[ { $sort: { _id: 1 } }, { $group: { _id: null, alphabet: { $accumulator: { init: function() { return "" }, accumulate: function(state, letter) { return(state + letter) }, accumulateArgs: [ "$_id" ], merge: function(state1, state2) { return(state1 + state2) }, lang: "js" } } } } ]
MongoDB では、ドキュメントがソート順で処理されることは保証されません。つまり、 alphabet
フィールドは必ずしもabc...z
に設定されるわけではありません。
この動作により、 $accumulator
関数ではドキュメントを特定の順序で処理して返す必要がなくなりました。
Javascript有効
$accumulator
を使用するには、サーバー側スクリプトを有効にする必要があります。
$accumulator
(または $function
、$where
、mapReduce
)を使用しない場合は、サーバー側スクリプトを無効にします。
mongod
インスタンスについては、security.javascriptEnabled
構成オプションまたは--noscripting
コマンドライン オプションを参照してください。mongos
インスタンスについては、security.javascriptEnabled
構成オプションまたは--noscripting
コマンドライン オプションを参照してください。In earlier versions, MongoDB does not allow JavaScript execution onmongos
instances.
➤ 安全な構成オプションを使用して MongoDB を実行するも参照してください。
例
$accumulator
を使用して 演算子を実装$avg
注意
この例では、 $accumulator
演算子を使用して$avg
演算子を実装する方法について説明します。この演算子は MongoDB ですでにサポートされています。 この例の目的は新しい機能を実装することではなく、 $accumulator
演算子の動作と構文を既知のロジックで説明することです。
mongosh
では、次のドキュメントを含むbooks
という名前のサンプル コレクションが作成されます。
db.books.insertMany([ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }, { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ])
次の操作では、ドキュメントをauthor
ごとにgroups
し、 $accumulator
を使用して各著者の書籍全体の平均コピー数を計算します。
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } }, accumulate: function(state, numCopies) { // Define how to update the state return { count: state.count + 1, sum: state.sum + numCopies } }, accumulateArgs: ["$copies"], // Argument required by the accumulate function merge: function(state1, state2) { // When the operator performs a merge, return { // add the fields from the two states count: state1.count + state2.count, sum: state1.sum + state2.sum } }, finalize: function(state) { // After collecting the results from all documents, return (state.sum / state.count) // calculate the average }, lang: "js" } } } } ])
結果
この操作は、次の結果を返します。
{ "_id" : "Dante", "avgCopies" : 1.6666666666666667 } { "_id" : "Homer", "avgCopies" : 10 }
動作
$accumulator
は、 count
とsum
の両方が0
に設定されている初期状態を定義します。 $accumulator
が処理するドキュメントごとに、状態を次の方法で更新します。
count
を1増やし、Adding the values of the document's
copies
field to thesum
. accumulate関数はcopies
フィールドにアクセスできます。これはaccumulateArgsフィールドで渡されるためです。
プロセスされるドキュメントごとに、accumulate 関数はアップデートした状態を返します。
すべてのドキュメントが処理されると、 finalize関数はコピーのsum
をドキュメントのcount
で割って平均を取得します。 This removes the need to keep a running computed average, since the finalize function receives the cumulative sum
and count
of all documents.
との比較 $avg
この操作は、 $avg
演算子を使用する次のパイプラインと同等です。
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $avg: "$copies" } } } ])
initArgs
を使用して初期状態をグループごとに変更する
でinitArgsオプションを使用すると、 $accumulator
の初期状態を変更できます。 これは、たとえば次のような場合に役立ちます。
自分の状態にないフィールドの値を使用して自分の状態に影響を与えるか、または
現在処理中のグループに基づいて、初期状態を異なる値に設定します。
mongosh
では、次のドキュメントを含むrestaurants
という名前のサンプル コレクションが作成されます。
db.restaurants.insertMany([ { "_id" : 1, "name" : "Food Fury", "city" : "Bettles", "cuisine" : "American" }, { "_id" : 2, "name" : "Meal Macro", "city" : "Bettles", "cuisine" : "Chinese" }, { "_id" : 3, "name" : "Big Crisp", "city" : "Bettles", "cuisine" : "Latin" }, { "_id" : 4, "name" : "The Wrap", "city" : "Onida", "cuisine" : "American" }, { "_id" : 5, "name" : "Spice Attack", "city" : "Onida", "cuisine" : "Latin" }, { "_id" : 6, "name" : "Soup City", "city" : "Onida", "cuisine" : "Chinese" }, { "_id" : 7, "name" : "Crave", "city" : "Pyote", "cuisine" : "American" }, { "_id" : 8, "name" : "The Gala", "city" : "Pyote", "cuisine" : "Chinese" } ])
あるアプリケーションで、ユーザーがこのデータをクエリしてレストランを検索できるとします。ユーザーが住んでいる都市の結果表示数を増やすと便利な場合があります。この例では、ユーザーの都市がuserProfileCity
という変数で呼び出されることを想定します。
次の集計パイプラインは、 city
のドキュメントをgroups
します。 この操作では$accumulator
を使用して、レストランの都市がユーザーのプロファイル内の都市と一致するかどうかに応じて、各都市からの異なる数の結果が表示されます。
1 db.restaurants.aggregate([ 2 { 3 $group : 4 { 5 _id : { city: "$city" }, 6 restaurants: 7 { 8 $accumulator: 9 { 10 init: function(city, userProfileCity) { // Set the initial state 11 return { 12 max: city === userProfileCity ? 3 : 1, // If the group matches the user's city, return 3 restaurants 13 restaurants: [] // else, return 1 restaurant 14 } 15 }, 16 17 initArgs: ["$city", <userProfileCity>], // Argument to pass to the init function 18 19 accumulate: function(state, restaurantName) { // Define how to update the state 20 if (state.restaurants.length < state.max) { 21 state.restaurants.push(restaurantName); 22 } 23 return state; 24 }, 25 26 accumulateArgs: ["$name"], // Argument required by the accumulate function 27 28 merge: function(state1, state2) { 29 return { 30 max: state1.max, 31 restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) 32 } 33 }, 34 35 finalize: function(state) { // Adjust the state to only return field we need 36 return state.restaurants 37 } 38 39 lang: "js" 40 } 41 } 42 } 43 } 44 ])
結果
userProfileCity
の値が Bettles
の場合、この操作は以下の結果を返します。
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury", "Meal Macro", "Big Crisp" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
userProfileCity
の値が Onida
の場合、この操作は以下の結果を返します。
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap", "Spice Attack", "Soup City" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
userProfileCity
の値が Pyote
の場合、この操作は以下の結果を返します。
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave", "The Gala" ] } }
userProfileCity
の値がその他の値の場合、この操作は次の結果を返します。
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
動作
初期max
restaurants
化関数は、 フィールドと フィールドを含む初期状態を定義します。max
フィールドには、その特定のグループのレストランの最大数が設定されます。 ドキュメントのcity
フィールドがuserProfileCity
と一致する場合、そのグループには最大3レストランが含まれます。 そうではなく、ドキュメント_id
がuserProfileCity
と一致しない場合、グループには最大で 1 件のレストランが含まれます。 初期化関数は、 initArgs配列からcity
userProfileCity
引数の両方を受け取ります。
$accumulator
が処理するドキュメントごとに、レストランのname
をrestaurants
配列にプッシュします。ただし、その名前によって、 restaurants
の長さがmax
値を超えない場合に限ります。 プロセスされるドキュメントごとに、 accumulate関数はアップデートした状態を返します。
merge関数は、2 つの状態をマージする方法を定義します。 この関数は各州のrestaurant
配列をまとめて連結し、結果の配列の長さは slice() max
を使用して制限されます。 メソッドを使用して、 値を超えないことを確認します。
すべてのドキュメントが処理されると、 finalize関数は結果の状態を変更し、レストランの名前のみを返すようにします。 この関数を使用しない場合、 max
フィールドも出力に含まれるため、アプリケーションのニーズは満たされません。