Docs Menu
Docs Home
/
MongoDBマニュアル
/ / /

$accumulator(集計)

項目一覧

  • 定義
  • 構文
  • 動作
$accumulator

カスタム アキュムレータ演算子を定義します。アキュムレーターは、ドキュメントがパイプラインを通って処理される際に、ドキュメントの状態(たとえば、合計、最大、最小、および関連データ)を維持する演算子です。MongoDB のクエリ言語でサポートされていない動作を実装するには、 $accumulator 演算子を使用して独自の JavaScript 関数を実行します。$function も参照してください。

$accumulator は、次のステージで使用できます。

  • $bucket

  • $bucketAuto

  • $group

重要

集計演算子内で JavaScript を実行すると、パフォーマンスが低下する可能性があります。 $accumulator提供された パイプライン演算子 が アプリケーションのニーズを満たせない場合にのみ、 演算子を使用してください。

$accumulator演算子の構文は次のとおりです。

{
$accumulator: {
init: <code>,
initArgs: <array expression>, // Optional
accumulate: <code>,
accumulateArgs: <array expression>,
merge: <code>,
finalize: <code>, // Optional
lang: <string>
}
}
フィールド
タイプ
説明
文字列またはコード

状態を初期化するために使用される関数。 init関数は、 initArgs配列式から引数を受け取ります。 関数の定義は、 BSONタイプのコードまたはstringとして指定できます。

init関数の形式は次のとおりです。

function (<initArg1>, <initArg2>, ...) {
...
return <initialState>
}
配列

オプション。init関数に渡される引数。

initArgs の形式は次のとおりです。

[ <initArg1>, <initArg2>, ... ]

重要$bucketAutoステージで使用する場合、initArgs はグループ・キーを参照できません(つまり、$<fieldName> 構文は使用できません)。 代わりに、 $bucketAutoステージでは、 initArgsに定数値のみを指定できます。

文字列またはコード

ドキュメントを累積するために使用される関数。 accumulate関数は、現在の状態とaccumulateArgs配列式から引数を受け取ります。 accumulate関数の結果が新しい状態になります。 関数の定義は、 BSONタイプのコードまたはstringとして指定できます。

accumulate関数の形式は次のとおりです。

function(state, <accumArg1>, <accumArg2>, ...) {
...
return <newState>
}
配列

accumulate関数に渡される引数。accumulateArgsを使用して、 accumulate関数に渡すフィールド値を指定できます。

accumulateArgs の形式は次のとおりです。

[ <accumArg1>, <accumArg2>, ... ]
文字列またはコード

2 つの内部状態をマージするために使用される関数。 mergeは、string またはコード BSON タイプのいずれかである必要があります。 mergeは、2 つの結合された状態を組み合わせた結果を返します。 マージ 関数が呼び出されるタイミングについては、 「 $mergeを使用して 2 つの状態をマージする」 を参照してください。

merge関数の形式は次のとおりです。

function (<state1>, <state2>) {
<logic to merge state1 and state2>
return <newState>
}
文字列またはコード

オプション。アキュムレーションの結果を更新するために使用される関数。

finalize関数の形式は次のとおりです。

function (state) {
...
return <finalState>
}
文字列

$accumulatorコードで使用される言語。

重要:現在、lang でサポートされている値は js だけです。

次の手順は、 $accumulator演算子がドキュメントを処理する方法の概要です。

  1. 演算子は、init 関数によって定義された初期状態から開始されます。

  2. 各ドキュメントについて、 演算子はaccumulate関数に基づいて状態をアップデートします。 accumulate関数の最初の引数は現在の状態で、追加の引数はaccumulateArgs配列で指定します。

  3. 演算子が複数の中間状態をマージする必要がある場合、マージ関数を実行します。 マージ関数が呼び出されるタイミングの詳細については、「 $mergeを使用して 2 つの状態をマージする 」を参照してください。

  4. finalize 関数が定義されている場合、すべてのドキュメントが処理され、それに応じて状態がアップデートされると、finalize は状態を最終出力に変換します。

$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関数ではドキュメントを特定の順序で処理して返す必要がなくなりました。

$accumulatorを使用するには、サーバー側スクリプトを有効にする必要があります。

$accumulator(または $function$wheremapReduce)を使用しない場合は、サーバー側スクリプトを無効にします。

安全な構成オプションを使用して MongoDB を実行するも参照してください。

注意

この例では、 $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は、 countsumの両方が0に設定されている初期状態を定義します。 $accumulatorが処理するドキュメントごとに、状態を次の方法で更新します。

  • countを1増やし、

  • Adding the values of the document's copies field to the sum. 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演算子を使用する次のパイプラインと同等です。

db.books.aggregate([
{
$group : {
_id : "$author",
avgCopies: { $avg: "$copies" }
}
}
])

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を使用して、レストランの都市がユーザーのプロファイル内の都市と一致するかどうかに応じて、各都市からの異なる数の結果が表示されます。

注意

この例をmongoshで実行するには、 initArgs<userProfileCity>を、 Bettlesなどの実際の都市の値を含む string に置き換えます。

1db.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レストランが含まれます。 そうではなく、ドキュメント_iduserProfileCityと一致しない場合、グループには最大で 1 件のレストランが含まれます。 初期化関数は、 initArgs配列からcity userProfileCity引数の両方を受け取ります。

$accumulatorが処理するドキュメントごとに、レストランのnamerestaurants配列にプッシュします。ただし、その名前によって、 restaurantsの長さがmax値を超えない場合に限ります。 プロセスされるドキュメントごとに、 accumulate関数はアップデートした状態を返します。

merge関数は、2 つの状態をマージする方法を定義します。 この関数は各州のrestaurant 配列をまとめて連結し、結果の配列の長さは slice() maxを使用して制限されます。 メソッドを使用して、 値を超えないことを確認します。

すべてのドキュメントが処理されると、 finalize関数は結果の状態を変更し、レストランの名前のみを返すようにします。 この関数を使用しない場合、 maxフィールドも出力に含まれるため、アプリケーションのニーズは満たされません。

戻る

$abs