集約 (aggregation)

based on v41

Mongoには、 count, distinctgroup by 操作をサーバーサイドで実行するファンクションがあります。   さらに集約をするために MapReduce を使うこともできます。

count

count() は、コレクション内で、クエリーにマッチするオブジェクトの件数を返します。ドキュメントセレクターが渡された場合、マッチするドキュメントの件数が返されます。

size()count() と似ていますが、 limit() と skip() をクエリーで指定することができます。

db.collection.count(selector); 

例:

print( "# of objects: " + db.mycollection.count() ); 
print( db.mycollection.count( {active:true} ); 

count は、セレクターで使われている条件にインデックスがある場合速いです。たとえば、 active でのcountを速くするためには、

db.mycollection.ensureIndex( {active:1} );

distinct

distinct(key) は 与えられた key のdistinctな値を返します。

distinctコマンドは、コレクション内で与えられたキーにマッチするdistinctな値を返します。

このコマンドは次の形式を取ります

{ distinct : <collection_name>, key : <key>[, query : <query>] }

多くのドライバーはdistinct用のヘルパーがありますが。

> db.addresses.save({"zip-code": 10010}) 
> db.addresses.save({"zip-code": 10010}) 
> db.addresses.save({"zip-code": 99701}) 

> // シェルヘルパー
> db.addresses.distinct("zip-code"); 
[ 10010, 99701 ]

distinct は、ネストされたキーも指定できます

> db.comments.save({"user": {"points": 25}}) 
db.comments.save({"user": {"points": 31}}) 
db.comments.save({"user": {"points": 25}}) 

> db.comments.distinct("user.points"); 
[ 25, 31 ] 

また、distinctに他のクエリー条件を加えることもできます。

> db.address.distinct( "zip-code" , { age : 30 } )

注意: distinct コマンドは、一つのBSONオブジェクトとして返します。 もし、結果が大きい場合 (4メガバイト以上)の場合、map/reduceを代わりに使ってください。

group

注意: 現在のところ、shardの環境では、group()の代わりにmap/reduceを必ず使ってください。

group はグループ化されたアイテムを配列で返します。このコマンドはSQLのgroup byと非常によく似てます。SQLでは、

select a,b,sum(c) csum from coll where active=1 group by a,b 

これに対応するMongoDBは以下のようになります。

db.coll.group( 
{key: { a:true, b:true }, 
cond: { active:1 }, 
reduce: function(obj,prev) { prev.csum += obj.c; }, 
initial: { csum: 0 } 
}); 

注意: この結果は、一つのBSONオブジェクトとして返されるので、結果はなるべく小さくしてください(10,000キー以内)。大きすぎる場合例外が発生します。制限なしでの大きなグルーピングのためには、 map/reduce を使ってください。

group は、次のフィールドを含む1つのオブジェクトをパラメータとして渡します。

  • key: group byをするフィールド
  • reduce: reduce はイテレートされたオブジェクトをさらに集計(reduce)することができます。reduceファンクションでのよくある操作としては、合計やカウントがあります。 reduce は二つの引数をとります。 イテレート中の現在のドキュメントと、集計のカウンター用のオブジェクトです。上記の例では、 objprev がそれにあたります。
  • initial: 集計用のカウンターオブジェクトを初期化します
  • keyf: グルーピングキーとして使われる"key object"を返す任意のファンクションを指定します。 key の代わりに、オブジェクトに存在しないメンバーのキーを指定します(または、組み込みのメンバーにアクセス)。 key の代わりにセットします。
  • cond: 行に対してtrueが返ってくるような条件を指定できます。 これは、 find() クエリーオブジェクトで重要です。 nullが指定された場合、 reduce ファンクションはコレクション内のすべての行に対して実行されます。
  • finalize: アイテムが返される直前のリザルトセットの中のアイテム毎に実行されるファンクションを設定できます。アイテム自体を更新することもできます(たとえば、 countとtotalフィールドからaverageを計算し追加するとか)。また、まったく別のオブジェクトを返すこともできます( _id とaverageフィールドから新しいオブジェクトを返す、とか)。 例として、 jstests/group3.js を見てください。

groupされたデータをソートするには、戻り値を単純にクライアントサイドでしてください。次の例は、 group() を使って、 count の実装の例です。

function gcount(collection, condition) { 
var res = 
db[collection].group( 
{ key: {}, 
initial: {count: 0}, 
reduce: function(obj,prev){ prev.count++;}, 
cond: condition } ); 
// group() returns an array of grouped items. here, there will be a single 
// item, as key is {}. 
return res[0] ? res[0].count : 0; 
} 

下記の例は、このようなデータを仮定しています。

{ domain: "www.mongodb.org" 
, invoked_at: {d:"2009-11-03", t:"17:14:05"} 
, response_time: 0.05 
, http_action: "GET /display/DOCS/Aggregation" 
} 

2009年11月の http_action の statを表示。

db.test.group( 
{ cond: {"invoked_at.d": {$gt: "2009-11", $lt: "2009-12"}} 
, key: {http_action: true} 
, initial: {count: 0, total_time:0} 
, reduce: function(doc, out){ out.count++; out.total_time+=doc.response_time } 
, finalize: function(out){ out.avg_time = out.total_time / out.count } 
} ); 

[ 
{ 
"http_action" : "GET /display/DOCS/Aggregation", 
"count" : 1, 
"total_time" : 0.05, 
"avg_time" : 0.05 
} 
] 

2009年11月のそれぞれの日のそれぞれのドメインのstatを表示。

db.test.group( 
{ cond: {"invoked_at.d": {$gt: "2009-11", $lt: "2009-12"}} 
, key: {domain: true, invoked_at.d: true} 
, initial: {count: 0, total_time:0} 
, reduce: function(doc, out){ out.count++; out.total_time+=doc.response_time } 
, finalize: function(out){ out.avg_time = out.total_time / out.count } 
} ); 

[ 
{ 
"http_action" : "GET /display/DOCS/Aggregation", 
"count" : 1, 
"total_time" : 0.05, 
"avg_time" : 0.05 
} 
] 

groupを様々な言語で使う

いくつかのドライバで、group helperファンクションを提供しています。   提供していないドライバでは、groupコマンドをdbコマンドに手動で直接発行することもできます。  Mongoシェルでの例です。

 > db.foo.find() 
{"_id" : ObjectId( "4a92af2db3d09cb83d985f6f") , "x" : 1} 
{"_id" : ObjectId( "4a92af2fb3d09cb83d985f70") , "x" : 3} 
{"_id" : ObjectId( "4a92afdab3d09cb83d985f71") , "x" : 3} 

> db.$cmd.findOne({group:{ns:"foo",cond:{},key:{x:1},initial:{count:0},$reduce:function(obj,prev){prev.count++;}}})
{"retval" : [{"x" : 1 , "count" : 1},{"x" : 3 , "count" : 2}] , "count" : 3 , "keys" : 2 , "ok" : 1} 

(key の代わりに) keyf を使う場合、 $を必ずプレフィクスとして付けてください。たとえば、

db.$cmd.findOne({group : {
... ns : "foo",
... $keyf : function(doc) { return {"x" : doc.x}; },
... initial : {count : 0},
... $reduce : function(obj,prev) { prev.count++; }}})

Map/Reduce

MongoDBは、もっと複雑な集約なために MapReduce を提供しています。CouchDBユーザへ: MongoDBの基本的なクエリーはmap/reduceを使わないことに注意してください。

参照


Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

IF YOU HAVE A QUESTION, POST IT TO THE USER GROUP.

These pages are fine for comments, but for questions, your best bet will always be the MongoDB User Group.

blog comments powered by Disqus