HOWTO · MongoDB

MongoDB でレコードを数える

MongoDBで全件または条件に一致するドキュメントを数える方法、グループ化した件数の集計と並べ替え、日付別の件数集計、カーソルの扱い方を解説します。

このページの内容

MongoDBで条件に一致するドキュメントを数えるには、mongoshでdb.collection.countDocuments(filter)を使うか、集計パイプラインで$matchの後に$countを置きます。全ドキュメントを数える場合はdb.collection.countDocuments({})を使います。件数を、フィールド別や日付別のグループ化など、より大きなクエリの一部として求める場合は集計パイプラインが便利です。

次の例を実行します:

scores = [88, 92, 97, 71, 79, 83]
passing = [score for score in scores if score > 80]
print(len(passing))
4

このPythonの例は、MongoDBには接続せず、小さなリストに同じ条件を適用します。MongoDBでの例は、後述の集計セクションにあります。

MongoDBの操作

MongoDBでは、データはコレクション内のドキュメントとして保存されます。件数を求める操作では、コレクションを選び、必要に応じて条件を適用し、件数またはグループ化した結果を返す読み取り処理を行います。例では、試験結果にscores、日付付きの取引にsalesを使います。これらのコレクション名やフィールド名は、データベースに合わせて置き換えてください。

MongoDBの集計

集計パイプラインでは、ドキュメントが順番に並んだステージを通過します。各ステージで、そこまで到達したドキュメントを絞り込んだり、グループ化、計算、並べ替えを行ったりできます。たとえば、aggregate([{ $match: { status: "paid" } }, { $group: { _id: "$customerId", orders: { $sum: 1 } } }])は、支払い済みの注文に絞り込んでから顧客ごとに件数を数えます。ここに示すコードはMongoDBシェルの構文であり、実行にはMongoDBサーバーまたはシェルが必要です。

MongoDBの$count

$countステージは、そこまで到達したドキュメントを数え、$countに指定した文字列をフィールド名とするドキュメントを出力します。条件に一致するドキュメントだけを数えるには、その前に$matchを置きます。前述の6件のスコアを使うと、対応するパイプラインはdb.scores.aggregate([{ $match: { score: { $gt: 80 } } }, { $count: "passing_scores" }])です。結果のドキュメントは{ "passing_scores": 4 }という形になります。passing_scoresは出力フィールドであり、変数ではありません。

ステージに到達するドキュメントがない場合、$countは結果ドキュメントを出力しません。結果に必ず数値のゼロを含める必要がある場合は、アプリケーション側で処理するか、パイプラインの形を調整してください。パイプラインで件数を表す別の方法として、$groupで入力ドキュメントごとに1を加算できます:[{ $group: { _id: null, myCount: { $sum: 1 } } }, { $project: { _id: 0, myCount: 1 } }]。この射影では、件数のフィールドを残して_idを除外します。

MongoDBのdb.collection.count()

mongoshでは、db.collection.count()は件数を返し、db.collection.count(query)はクエリ条件を適用します。たとえば、db.orders.count({ status: "paid" })は条件に一致する注文を数えます。MongoDB 4.0でこのメソッドは非推奨になりました。現在のドキュメントでは、条件に一致する件数を正確に求めるにはcountDocuments()、コレクション全体のメタデータに基づく概算をすばやく得るにはestimatedDocumentCount()が推奨されています。db.collection.count()はトランザクション内では使用できません。その場合は代わりにcountDocuments()を使います。ドライバーによってメソッド名が異なることがあるため、アプリケーションで使うドライバーのドキュメントを確認してください。件数をパイプラインのステージと組み合わせる場合や、グループ化した値と一緒に返す場合はaggregate()を使います。MongoDBのcount()のリファレンスとcountDocuments()のリファレンスも参照してください。

シャーディングされたクラスター

MongoDBのドキュメントによると、孤立ドキュメントがある場合やチャンクの移行中は、シャーディングされたクラスターでcount()が不正確な結果を返すことがあります。クエリに一致する件数にはcountDocuments()を使ってください。この注意点はcount()のドキュメントで、推奨される代替手段はcountDocuments()のドキュメントで確認できます。ここでの説明は現行マニュアルに基づいているため、使用中のサーバーバージョンに対応するドキュメントも確認してください。

インデックスの使用

件数を求める条件にインデックスが役立つかどうかは、クエリ、利用可能なインデックス、選択された実行計画によって異なります。explain()で実際の処理を調べ、MongoDBのクエリ最適化ガイドを確認してください。インデックスがあっても、インデックスキーだけで件数を求められるとは限りません。

予期しないシャットダウン後の正確性

MongoDBのドキュメントによると、count()はコレクションのメタデータに依存する場合があるため、正常でないシャットダウンの後に不正確な結果を返すことがあります。条件に一致するドキュメントの件数が必要な場合は、条件を満たすドキュメントを数えるcountDocuments()を使ってください。validateはコレクションのデータとインデックスを検査する診断機能であり、件数クエリの代わりにはなりません。復旧作業を行う前に、count()の注意点と、バージョンに応じたvalidateコマンドのガイドを確認してください。

クライアントの切断

件数の応答がクライアントに届く前に接続が切れた場合、クライアントは返された件数を確認できません。使用するドライバーのガイドに従ってエラーを処理し、応答がないことを件数が確定した証拠として扱わないでください。MongoDBのドライバーのドキュメントでは、ドライバーごとの動作やAPIの詳細を確認できます。

コレクション内のすべてのドキュメントを数える

集計ステージを使ってorders内のすべてのドキュメントを数えるには、db.orders.aggregate([{ $count: "total" }])を使います。アプリケーションで数値の件数を取得するには、使用言語に対応したMongoDBドライバーの件数取得APIを使ってください。mongoshではdb.orders.countDocuments({})を使うと、全ドキュメントを対象とする条件が明確になります。

クエリ条件に一致するすべてのドキュメントを数える

条件に一致するドキュメントだけを数えるには、フィルターを渡します。たとえば、db.orders.countDocuments({ orderDate: { $gte: ISODate("2024-01-01T00:00:00Z"), $lt: ISODate("2025-01-01T00:00:00Z") } })は、UTCで2024年に該当する注文を数えます。下限は含まれ、上限は含まれないため、2025年の開始時刻のタイムスタンプは対象になりません。

MongoDBでドキュメントをグループ化して数える

次のstudentsドキュメントを使って、具体例を示します:{ name: "Ava", city: "Paris", courses: ["math", "science"] }、{ name: "Ben", city: "Paris", courses: ["math"] }、{ name: "Cam", city: "Lyon", courses: ["science", "history"] }。これらは説明用の入力ドキュメントです。以下のMongoDBシェルのパイプラインは、この記事にあるローカルのPython例では実行されません。

フィールドの異なる値ごとにドキュメントを数えるには、$groupと$sum: 1を使います。db.students.aggregate([{ $group: { _id: "$city", students: { $sum: 1 } } }])は、{ "_id": "Paris", "students": 2 }と{ "_id": "Lyon", "students": 1 }を返します。_id: nullのような定数のグループキーを使うと、入力全体が1つのグループになります。フィールドごとのグループ化にはなりません。

MongoDBでグループごとの件数を並べ替える

$sortByCountは、式に基づいてドキュメントをグループ化し、各グループの件数を数えてから、件数の降順に結果を並べ替えます。サンプルのcourses配列に対してdb.students.aggregate([{ $unwind: "$courses" }, { $sortByCount: "$courses" }])を使うと、まず配列の要素ごとに1つのパイプラインドキュメントが出力されます。期待される結果は{ "_id": "math", "count": 2 }、{ "_id": "science", "count": 2 }、{ "_id": "history", "count": 1 }です(件数が同じ2つのグループは、どちらの順でも表示されることがあります)。$unwindにより、配列の各要素が個別に件数へ加算されます。同じ処理を明示的なステージで書くと、[{ $unwind: "$courses" }, { $group: { _id: "$courses", count: { $sum: 1 } } }, { $sort: { count: -1 } }]となります。

MongoDBで複数のフィールドごとにグループ化して数える

異なるフィールドの組み合わせごとに数えるには、各グループ化フィールドを_idドキュメントに指定します。たとえば、リクエストのドキュメントが{ source: "web", status: "ok" }、{ source: "web", status: "error" }、{ source: "web", status: "ok" }の場合、db.requests.aggregate([{ $group: { _id: { source: "$source", status: "$status" }, requests: { $sum: 1 } } }, { $sort: { "_id.source": 1, "_id.status": 1 } }])は、{ "_id": { "source": "web", "status": "error" }, "requests": 1 }と{ "_id": { "source": "web", "status": "ok" }, "requests": 2 }を返します。$countはそこまで到達したパイプラインドキュメントを数えます。グループ内の件数を求めるには、$groupと$sum: 1を使います。

MongoDBで日付ごとにグループ化して数える

日ごとの注文数と売上合計を求めるには、年、月、日の形式にした日付でグループ化します。次のsalesドキュメントでは、UTCの日付と数値のprice、quantityフィールドを使います:{ date: ISODate("2024-03-01T12:00:00Z"), price: 10, quantity: 2 }、{ date: ISODate("2024-03-01T18:00:00Z"), price: 5, quantity: 1 }、{ date: ISODate("2024-04-02T09:00:00Z"), price: 7, quantity: 3 }。開始時刻を含み終了時刻を含まない期間により、2024年3月と4月が対象になり、2024年5月1日は除外されます。

db.sales.aggregate([{ $match: { date: { $gte: ISODate("2024-03-01T00:00:00Z"), $lt: ISODate("2024-05-01T00:00:00Z") } } }, { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date", timezone: "UTC" } }, totalSales: { $sum: { $multiply: ["$price", "$quantity"] } }, orderCount: { $sum: 1 } } }, { $sort: { _id: 1 } }])を実行します。

これらのドキュメントから得られる結果は、{ "_id": "2024-03-01", "totalSales": 25, "orderCount": 2 }と{ "_id": "2024-04-02", "totalSales": 21, "orderCount": 1 }です。各日の合計は、その日のprice * quantityの合計です。保存されている日付や業務上の日付が別のタイムゾーンを基準にする場合は、期間の境界とグループ化に使うタイムゾーンを一貫させてください。このクエリはMongoDBシェルの構文で、説明したフィールドを含むデータベースが必要です。結果は上のサンプルドキュメントから算出したものであり、実行時の証拠に基づくものではありません。

toArray()でカーソルを配列に変換する

toArray()はカーソルに残っている結果を読み取り、配列として返します。結果を複数回参照したり反復処理したりする必要がある場合に便利ですが、ドキュメントはまとめてアプリケーションのメモリに保持されます。結果が大量にある場合は、一度にすべてを配列化せず、カーソルを反復処理してください。mongoshでdb.products.find({ active: true }).toArray()を使うと、条件に一致する商品ドキュメントの配列が返されます。これは件数を数える操作ではありません。

itcount()でカーソルに残っているドキュメントを数える

mongoshのitcount()は、カーソルに残っている結果を消費しながらドキュメントを数えます。対話型シェルでカーソルを調べるときに便利ですが、実行後にカーソルは使い切られます。これは再利用できる件数取得クエリではありません。アプリケーションでカーソルを消費せずに件数を取得する必要がある場合、引き続き使うカーソルに対してcountDocuments()の代わりに使うことはできません。