最も単純な (そして最もスケーラブルな) ソリューションは、おそらくフィルタリング条件を MongoDB クエリに変換し、クライアント側で集計を行うことです。
上記の例を取り上げて、それを分解して MongoDB クエリを作成しましょう ( PyMongoを使用してこれを示しますが、必要に応じて Mongoengine または別の ODM を使用して同じことを行うこともできます)。
WHERE col1=1 AND col2="foo" OR col3 > "2012-01-01 00:00:00" OR col3 < "2012-01-02 00:00:00" -- 条件
これは、PyMongo のfind()
メソッドの最初の引数です。$or
演算子を使用して論理 AND/OR ツリーを明示的に構築する必要があります。
from bson.tz_util import utc
cursor = db.collection.find({'$or': [
{'col1': 1, 'col2': 'foo'},
{'col3': {'$gt': datetime(2012, 01, 01, tzinfo=utc)}},
{'col3': {'$lt': datetime(2012, 01, 02, tzinfo=utc)}},
]})
日付/時刻フィールドと比較する場合、MongoDB は文字列を日付に変換しないことに注意してください。したがって、ここでは Pythondatetime
モジュールを使用して明示的に変換しています。そのモジュールのdatetime
クラスは、指定されていない引数のデフォルト値として 0 を想定しています。
SELECT col1, col2 -- 結果列
フィールド選択を使用して、必要なフィールドのみを取得できます。
from bson.tz_util import utc
cursor = db.collection.find({'$or': [
{'col1': 1, 'col2': 'foo'},
{'col3': {'$gt': datetime(2012, 01, 01, tzinfo=utc)}},
{'col3': {'$lt': datetime(2012, 01, 02, tzinfo=utc)}},
]}, fields=['col1', 'col2'])
GROUP BY col4、col5 -- グループ化ステートメント
これは、標準の MongoDB クエリを使用して効率的に実行することはできません (ただし、新しいAggregation Frameworkを使用してサーバー側でこれをすべて実行する方法についてはすぐに説明します)。代わりに、これらの列でグループ化したいことがわかっているので、これらのフィールドで並べ替えることで、アプリケーション コードをより単純にすることができます。
from bson.tz_util import utc
from pymongo import ASCENDING
cursor = db.collection.find({'$or': [
{'col1': 1, 'col2': 'foo'},
{'col3': {'$gt': datetime(2012, 01, 01, tzinfo=utc)}},
{'col3': {'$lt': datetime(2012, 01, 02, tzinfo=utc)}},
]}, fields=['col1', 'col2', 'col4', 'col5'])
cursor.sort([('col4', ASCENDING), ('col5', ASCENDING)])
ORDER BY col1 DESC, col2 ASC -- order by ステートメント
これは、必要な集計関数を適用した後にアプリケーション コードで実行する必要があります (col4 を合計し、col5 の最大値を取得するとします)。
from bson.tz_util import utc
from pymongo import ASCENDING
cursor = db.collection.find({'$or': [
{'col1': 1, 'col2': 'foo'},
{'col3': {'$gt': datetime(2012, 01, 01, tzinfo=utc)}},
{'col3': {'$lt': datetime(2012, 01, 02, tzinfo=utc)}},
]}, fields=['col1', 'col2', 'col4', 'col5'])
cursor.sort([('col4', ASCENDING), ('col5', ASCENDING)])
# groupby REQUIRES that the iterable be sorted to work
# correctly; we've asked Mongo to do this, so we don't
# need to do so explicitly here.
from itertools import groupby
groups = groupby(cursor, keyfunc=lambda doc: (doc['col1'], doc['col2'])
out = []
for (col1, col2), docs in groups:
col4sum = 0
col5max = float('-inf')
for doc in docs:
col4sum += doc['col4']
col5max = max(col5max, doc['col5'])
out.append({
'col1': col1,
'col2': col2,
'col4sum': col4sum,
'col5max': col5max
})
集約フレームワークの使用
MongoDB 2.1 以降を使用している場合 (2.1.x は、まもなく予定されている 2.2.0 安定版リリースに向けた開発シリーズです)、Aggregation Framework を使用して、サーバー側でこれらすべてを実行できます。これを行うには、次のaggregate
コマンドを使用します。
from bson.son import SON
from pymongo import ASCENDING, DESCENDING
group_key = SON([('col4', '$col4'), ('col5': '$col5')])
sort_key = SON([('$col1', DESCENDING), ('$col2', ASCENDING)])
db.command('aggregate', 'collection_name', pipeline=[
# this is like the WHERE clause
{'$match': {'$or': [
{'col1': 1, 'col2': 'foo'},
{'col3': {'$gt': datetime(2012, 01, 01, tzinfo=utc)}},
{'col3': {'$lt': datetime(2012, 01, 02, tzinfo=utc)}},
]}},
# SELECT sum(col4), max(col5) ... GROUP BY col4, col5
{'$group': {
'_id': group_key,
'col4sum': {'$sum': '$col4'},
'col5max': {'$max': '$col5'}}},
# ORDER BY col1 DESC, col2 ASC
{'$sort': sort_key}
])
このaggregate
コマンドは、MongoDB の通常の制限の対象となる BSON ドキュメント (つまり、Python 辞書) を返します。返されるドキュメントのサイズが 16MB を超える場合、コマンドは失敗します。さらに、メモリ内$sort
の並べ替え (この集計の最後に によって必要とされる) の場合、並べ替えがサーバー上の物理 RAM の 10% を超える必要がある場合、集計フレームワークは失敗します (これは、コストのかかる集計が削除されるのを防ぐためです)。 Mongo がデータ ファイルに使用するすべてのメモリ)。