$group(集計)
定義
$group
$group
ステージは「グループ キー」に従ってドキュメントをグループに分けます。ユニークなグループ キーごとに 1 つのドキュメントを出力します。グループキーは、多くの場合、フィールドまたはフィールドのグループです。グループキーは、式の結果にすることもできます。グループキーを設定するには、
$group
パイプライン ステージの_id
フィールドを使用します。の使用例については以下を参照してください。$group
ステージの出力では、_id
フィールドにそのドキュメントのグループ キーが設定されます。出力ドキュメントには、アキュムレータ式 を使用して設定された追加のフィールドを含めることもできます。
注意
$group
は出力ドキュメントを順序付けしません。
互換性
次の環境でホストされる配置には $group
を使用できます。
MongoDB Atlas はクラウドでの MongoDB 配置のためのフルマネージド サービスです
MongoDB Enterprise: サブスクリプションベースの自己管理型 MongoDB バージョン
MongoDB Community: ソースが利用可能で、無料で使用できる自己管理型の MongoDB のバージョン
構文
$group
ステージのプロトタイプ形式は次のとおりです。
{ $group: { _id: <expression>, // Group key <field1>: { <accumulator1> : <expression1> }, ... } }
フィールド | 説明 |
---|---|
| 必須。 |
|
_id
とアキュムレータ 演算子は、有効な任意のexpression
を受け入れることができます。式の詳細については、式演算子を参照してください。
Considerations
パフォーマンス
$group
はブロッキング ステージであり、パイプラインはデータを処理する前にすべての入力データが検索されるまで待機します。ブロッキング ステージは、複数のステージを持つパイプラインの並列処理を減らすため、パフォーマンスを低下させる可能性があります。ブロッキング ステージでは、大規模なデータセットに対して大量のメモリが使用される場合もあります。
アキュムレータ演算子
<accumulator>
演算子は、次のアキュムレータ 演算子のいずれかである必要があります。
バージョン 5.0 での変更。
名前 | 説明 |
---|---|
ユーザー定義のアキュムレータ関数の結果を返します。 | |
グループごとのユニークな式値の配列を返します。配列要素の順序は未定義です。 バージョン 5.0 で変更、 | |
数値の平均を返します。数値以外の値は無視されます。 バージョン 5.0 で変更、 | |
グループにあるドキュメントの数を返します。
バージョン 5.0 で追加され、 | |
グループ内の最初のドキュメントの式の結果を返します。 バージョン 5.0 で変更、 | |
グループ内の最初の バージョン 5.2 で追加され、 | |
グループ内の最後のドキュメントの式の結果を返します。 バージョン 5.0 で変更、 | |
グループ内の最後の バージョン 5.2 で追加され、 | |
グループごとの最大の式の値を返します。 バージョン 5.0 で変更、 | |
グループ内の最大値を持つ バージョン 5.2 で追加。 | |
各グループの入力ドキュメントを組み合わせて作成したドキュメントを返します。 | |
グループごとの最小の式値を返します。 バージョン 5.0 で変更、 | |
グループごとのドキュメントの式値の配列を返します。 バージョン 5.0 で変更、 | |
入力値の母集団標準偏差を返します。 バージョン 5.0 で変更、 | |
入力値のサンプル標準偏差を返します。 バージョン 5.0 で変更、 | |
数値の合計を返します。数値以外の値は無視されます。 バージョン 5.0 で変更、 | |
$group
およびメモリ制限
$group
ステージが100メガバイトの RAM を超える場合、MongoDB は一時ファイルにデータを書込みます。 ただし、 allowDiskUseオプションがfalse
に設定されている場合、 $group
はエラーを返します。 詳細については、「集計パイプラインの制限 」を参照してください。
$group
パフォーマンスの最適化
このセクションでは、 $group
のパフォーマンスを向上させるための最適化について説明します。 手動で行うことができる最適化と、MongoDB が内部で行う最適化があります。
各グループの最初または最後のドキュメントを返すための最適化
パイプラインsorts
とgroups
が同じフィールドで実行され、 $group
ステージで$first
または$last
アキュムレータ 演算子のみが使用される場合は、並べ替え順序に一致するグループ化されたフィールドにインデックスを追加することを検討してください。場合によっては、$group
ステージでインデックスを使用して各グループの最初または最後のドキュメントをすばやく見つけることができます。
例
foo
という名前のコレクションにインデックス { x: 1, y: 1 }
が含まれている場合、次のパイプラインはそのインデックスを使用して各グループの最初のドキュメントを検索できます。
db.foo.aggregate([ { $sort:{ x : 1, y : 1 } }, { $group: { _id: { x : "$x" }, y: { $first : "$y" } } } ])
スロットベースのクエリ実行エンジン
バージョン 5.2 以降、MongoDB は、次のいずれかの場合にスロットベースの実行クエリ エンジンを使用して$group
ステージを実行します。
$group
はパイプラインの第一ステージです。パイプラインの先行ステージもすべて、スロットベースの実行エンジンで実行できます。
詳細については、「$group
最適化」を参照してください。
例
コレクション内のドキュメント数のカウント
mongosh
では、次のドキュメントを含むsales
という名前のサンプル コレクションが作成されます。
db.sales.insertMany([ { "_id" : 1, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("2"), "date" : ISODate("2014-03-01T08:00:00Z") }, { "_id" : 2, "item" : "jkl", "price" : Decimal128("20"), "quantity" : Int32("1"), "date" : ISODate("2014-03-01T09:00:00Z") }, { "_id" : 3, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32( "10"), "date" : ISODate("2014-03-15T09:00:00Z") }, { "_id" : 4, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") }, { "_id" : 5, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") }, { "_id" : 6, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") }, { "_id" : 7, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("10") , "date" : ISODate("2015-09-10T08:43:00Z") }, { "_id" : 8, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") }, ])
次の集計操作では、 $group
ステージを使用してsales
コレクション内のドキュメントの数をカウントします。
db.sales.aggregate( [ { $group: { _id: null, count: { $count: { } } } } ] )
この操作では、次の結果を返します。
{ "_id" : null, "count" : 8 }
この集計操作は、次の SQL ステートメントと同等です。
SELECT COUNT(*) AS count FROM sales
Retrieve Distinct Values
次の集計操作では、 $group
ステージを使用して、 sales
コレクションから個別のアイテム値を取得します。
db.sales.aggregate( [ { $group : { _id : "$item" } } ] )
この操作では、次の結果を返します。
{ "_id" : "abc" } { "_id" : "jkl" } { "_id" : "def" } { "_id" : "xyz" }
注意
$group
を使用して シャーディングされたシャーディングされたコレクション内の個別の値を検索する場合、操作の結果が になると、結果にはDISTINCT_SCAN
孤立したドキュメント が含まれる可能性があります。
The only semantically correct パイプライン that is impacted is effectively a logical equivalent of a command, where there isdistinct
a$group
stage at or near the beginning of the パイプライン and the$group
is not preceded by a stage$sort
。
例、次の形式の $group
操作では DISTINCT_SCAN
が生成されます。
{ $group : { _id : "$<field>" } }
個別の値を取得するための動作の詳細については、個別の コマンドの動作を参照してください。
操作の結果がDISTINCT_SCAN
になるかどうかを確認するには、操作の explain 結果 を確認します。
Having を使用したアイテムのグループ化
次の集計操作では、item
フィールドでドキュメントをグループ化し、アイテムごとの合計売上額を計算し、合計売上額が 100 以上のアイテムのみを返します。
db.sales.aggregate( [ // First Stage { $group : { _id : "$item", totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } } } }, // Second Stage { $match: { "totalSaleAmount": { $gte: 100 } } } ] )
- 第 1 ステージ:
$group
ステージでは、ドキュメントをitem
でグループ化し、個別のアイテム値を取得します。 このステージでは、各アイテムのtotalSaleAmount
が返されます。- 第 2 ステージ:
$match
ステージでは、結果のドキュメントをフィルタリングして、totalSaleAmount
が 100 以上のアイテムのみを返します。
この操作では、次の結果を返します。
{ "_id" : "abc", "totalSaleAmount" : Decimal128("170") } { "_id" : "xyz", "totalSaleAmount" : Decimal128("150") } { "_id" : "def", "totalSaleAmount" : Decimal128("112.5") }
この集計操作は、次の SQL ステートメントと同等です。
SELECT item, Sum(( price * quantity )) AS totalSaleAmount FROM sales GROUP BY item HAVING totalSaleAmount >= 100
件数、合計、および平均の計算
mongosh
では、次のドキュメントを含むsales
という名前のサンプル コレクションが作成されます。
db.sales.insertMany([ { "_id" : 1, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("2"), "date" : ISODate("2014-03-01T08:00:00Z") }, { "_id" : 2, "item" : "jkl", "price" : Decimal128("20"), "quantity" : Int32("1"), "date" : ISODate("2014-03-01T09:00:00Z") }, { "_id" : 3, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32( "10"), "date" : ISODate("2014-03-15T09:00:00Z") }, { "_id" : 4, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") }, { "_id" : 5, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") }, { "_id" : 6, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") }, { "_id" : 7, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("10") , "date" : ISODate("2015-09-10T08:43:00Z") }, { "_id" : 8, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") }, ])
日付別にグループ化
以下のパイプラインは、2014 年の各日の合計売上額、平均売上数量、売上件数を計算します。
db.sales.aggregate([ // First Stage { $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } } }, // Second Stage { $group : { _id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }, count: { $sum: 1 } } }, // Third Stage { $sort : { totalSaleAmount: -1 } } ])
- 第 1 ステージ:
$match
ステージでは、ドキュメントをフィルタリングして、2014 年のドキュメントのみを次のステージに渡します。- 第 2 ステージ:
$group
ステージでは、ドキュメントを日付別にグループ化し、各グループの合計販売額、平均数量、およびドキュメントの合計数を計算します。- 第 3 ステージ:
$sort
ステージでは、各グループの合計売上額の降順で結果をソートします。
この操作は次の結果を返します。
{ "_id" : "2014-04-04", "totalSaleAmount" : Decimal128("200"), "averageQuantity" : 15, "count" : 2 } { "_id" : "2014-03-15", "totalSaleAmount" : Decimal128("50"), "averageQuantity" : 10, "count" : 1 } { "_id" : "2014-03-01", "totalSaleAmount" : Decimal128("40"), "averageQuantity" : 1.5, "count" : 2 }
この集計操作は、次の SQL ステートメントと同等です。
SELECT date, Sum(( price * quantity )) AS totalSaleAmount, Avg(quantity) AS averageQuantity, Count(*) AS Count FROM sales WHERE date >= '01/01/2014' AND date < '01/01/2015' GROUP BY date ORDER BY totalSaleAmount DESC
グループ化 null
次の集計操作では、null
のグループ _id
を指定して、コレクション内のすべてのドキュメントの合計売上額、平均数量、および件数を計算します。
db.sales.aggregate([ { $group : { _id : null, totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }, count: { $sum: 1 } } } ])
この操作では、次の結果を返します。
{ "_id" : null, "totalSaleAmount" : Decimal128("452.5"), "averageQuantity" : 7.875, "count" : 8 }
この集計操作は、次の SQL ステートメントと同等です。
SELECT Sum(price * quantity) AS totalSaleAmount, Avg(quantity) AS averageQuantity, Count(*) AS Count FROM sales
Pivot Data
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 } ])
title
を author
でグループ化
次の集計操作では、books
コレクションのデータをピボットして、タイトルを著者ごとにグループ化します。
db.books.aggregate([ { $group : { _id : "$author", books: { $push: "$title" } } } ])
この操作により、次のドキュメントが返されます。
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] } { "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
ドキュメントのグループ化 author
次の集計操作はドキュメントを author
ごとにグループ化します。
db.books.aggregate([ // First Stage { $group : { _id : "$author", books: { $push: "$$ROOT" } } }, // Second Stage { $addFields: { totalCopies : { $sum: "$books.copies" } } } ])
- 第 1 ステージ:
$group
は$$ROOT
システム変数を使用して、ドキュメント全体を著者ごとにグループ化します。 このステージでは、次のドキュメントを次のステージに渡します。{ "_id" : "Homer", "books" : [ { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ] }, { "_id" : "Dante", "books" : [ { "_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 } ] } - 第 2 ステージ:
$addFields
出力に各著者の書籍の合計部数を含むフィールドを追加します。注意
結果のドキュメントは、BSON ドキュメント サイズの制限である 16 メガバイトを超えてはなりません。
この操作により、次のドキュメントが返されます。
{ "_id" : "Homer", "books" : [ { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ], "totalCopies" : 20 } { "_id" : "Dante", "books" : [ { "_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 } ], "totalCopies" : 5 }
追加リソース
郵便番号データセットによる集計チュートリアルでは、一般的なユースケースにおける$group
演算子の幅広い例を紹介しています。