著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

MongoDBデータベースに保存されているドキュメントは、大きく異なる可能性があります。 ショッピングリストのアイテムのように、比較的小さく、エントリが少ないものもあります。 他のものは非常に複雑で、異なるタイプの数十のフィールド、複数の値を保持する配列、さらにはより大きな構造内にネストされた他のドキュメントを含む場合があります。

ドキュメントの複雑さや数に関係なく、ほとんどの場合、すべてのドキュメントのデータを一度に確認する必要はありません。 代わりに、1つ以上の特定の条件を満たすドキュメントのみを取得することをお勧めします。 海辺からの距離、ペットにやさしいプール、近くの駐車場など、予約Webサイトでさまざまなフィルターを選択して休暇の目的地を見つける方法と同様に、MongoDBに正確にクエリを実行して、必要なドキュメントを正確に見つけることができます。 。 MongoDBは、ドキュメントを取得するときにフィルタリング基準を定義するための堅牢なクエリメカニズムを提供します。

このチュートリアルでは、さまざまな範囲のフィルターと条件を使用してMongoDBコレクションをクエリする方法を学習します。 また、カーソルとは何か、およびMongoDBシェル内でカーソルを使用する方法についても学習します。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • sudo権限を持つ通常の非rootユーザーと、UFWで構成されたファイアウォールを備えたサーバー。 このチュートリアルは、Ubuntu 20.04を実行しているサーバーを使用して検証されており、Ubuntu20.04のこの初期サーバーセットアップチュートリアルに従ってサーバーを準備できます。
  • サーバーにインストールされているMongoDB。 これを設定するには、 Ubuntu20.04にMongoDBをインストールする方法に関するチュートリアルに従ってください。
  • 認証を有効にして管理ユーザーを作成することにより、サーバーのMongoDBインスタンスを保護します。 このようにMongoDBを保護するには、 Ubuntu20.04でMongoDBを保護する方法に関するチュートリアルに従ってください。
  • MongoDB CRUD操作に精通しており、特にコレクションからオブジェクトを取得している。 MongoDBシェルを使用してCRUD操作を実行する方法については、チュートリアルMongoDBでCRUD操作を実行する方法に従ってください。

注:サーバーの構成、インストール、およびMongoDBの安全なインストールの方法に関するリンクされたチュートリアルは、Ubuntu20.04を参照しています。 このチュートリアルは、基盤となるオペレーティングシステムではなく、MongoDB自体に焦点を当てています。 通常、認証が有効になっている限り、オペレーティングシステムに関係なく、すべてのMongoDBインストールで機能します。

ステップ1—サンプルデータベースの準備

MongoDBでクエリを作成する方法(複数のフィールド、ネストされたドキュメント、配列を持つドキュメントをフィルタリングする方法など)を説明するために、このガイドでは、世界で最も高い5つの山を説明するドキュメントのコレクションを含むサンプルデータベースを使用します。

このサンプルコレクションを作成するには、管理ユーザーとしてMongoDBシェルに接続します。 このチュートリアルは、前提条件 MongoDBセキュリティチュートリアルの規則に従い、この管理ユーザーの名前が AdminSammy であり、その認証データベースがadminであることを前提としています。 異なる場合は、次のコマンドでこれらの詳細を変更して、独自の設定を反映させてください。

  1. mongo -u AdminSammy -p --authenticationDatabase admin

プロンプトが表示されたら、管理ユーザーを作成したときに設定したパスワードを入力します。 パスワードを入力すると、プロンプトが大なり記号(>)に変わります。

注:新しい接続では、MongoDBシェルはデフォルトでtestデータベースに自動的に接続します。 このデータベースを安全に使用して、MongoDBとMongoDBシェルを試すことができます。

または、別のデータベースに切り替えて、このチュートリアルに記載されているすべてのコマンド例を実行することもできます。 別のデータベースに切り替えるには、useコマンドに続けて、データベースの名前を実行します。

  1. use database_name

MongoDBが複数のフィールド、ネストされたドキュメント、配列を持つドキュメントをフィルタリングする方法を理解するには、さまざまなタイプのクエリを探索できるように十分に複雑なサンプルデータが必要です。 前述のように、このガイドでは、世界で最も高い5つの山のサンプルコレクションを使用しています。

このコレクションのドキュメントは、この形式に従います。 このサンプルドキュメントでは、エベレストについて説明しています。

エベレスト文書
{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

このドキュメントには、次のフィールドと値が含まれています。

  • name:ピークの名前
  • height:ピークの標高(メートル単位)
  • location:山が位置する国。 このフィールドには、複数の国にある山を許可するための配列として値が格納されます
  • ascents:このフィールドの値は別のドキュメントです。 あるドキュメントがこのように別のドキュメント内に保存されている場合、それは埋め込みまたはネストされたドキュメントと呼ばれます。 各ascentsドキュメントは、指定された山の成功した登山について説明しています。 具体的には、各ascentsドキュメントには、totalフィールドが含まれており、各ピークの成功した上昇の総数が一覧表示されます。 さらに、これらのネストされたドキュメントのそれぞれには、値がネストされたドキュメントでもある2つのフィールドが含まれます。
      first:このフィールドの値は、最初の全体的に成功した上昇の年を表す1つのフィールドyearを含むネストされたドキュメントです。 valueはネストされたドキュメントであり、年フィールドも含まれています。このフィールドの値は、指定された山の最初の冬の登山に成功した年を表します

現在は年のみが含まれているにもかかわらず、最初の登頂がネストされたドキュメントとして表示される理由は、サミットの名前や遠征の詳細など、将来、より多くのフィールドで登頂の詳細を拡張しやすくするためです。

MongoDBシェルで次のinsertMany()メソッドを実行して、peaksという名前のコレクションを同時に作成し、それに5つのサンプルドキュメントを挿入します。 これらの文書は、世界で最も高い5つの山頂について説明しています。

  1. db.peaks.insertMany([
  2. {
  3. "name": "Everest",
  4. "height": 8848,
  5. "location": ["Nepal", "China"],
  6. "ascents": {
  7. "first": {
  8. "year": 1953
  9. },
  10. "first_winter": {
  11. "year": 1980
  12. },
  13. "total": 5656
  14. }
  15. },
  16. {
  17. "name": "K2",
  18. "height": 8611,
  19. "location": ["Pakistan", "China"],
  20. "ascents": {
  21. "first": {
  22. "year": 1954
  23. },
  24. "first_winter": {
  25. "year": 1921
  26. },
  27. "total": 306
  28. }
  29. },
  30. {
  31. "name": "Kangchenjunga",
  32. "height": 8586,
  33. "location": ["Nepal", "India"],
  34. "ascents": {
  35. "first": {
  36. "year": 1955
  37. },
  38. "first_winter": {
  39. "year": 1986
  40. },
  41. "total": 283
  42. }
  43. },
  44. {
  45. "name": "Lhotse",
  46. "height": 8516,
  47. "location": ["Nepal", "China"],
  48. "ascents": {
  49. "first": {
  50. "year": 1956
  51. },
  52. "first_winter": {
  53. "year": 1988
  54. },
  55. "total": 461
  56. }
  57. },
  58. {
  59. "name": "Makalu",
  60. "height": 8485,
  61. "location": ["China", "Nepal"],
  62. "ascents": {
  63. "first": {
  64. "year": 1955
  65. },
  66. "first_winter": {
  67. "year": 2009
  68. },
  69. "total": 361
  70. }
  71. }
  72. ])

出力には、新しく挿入されたオブジェクトに割り当てられたオブジェクト識別子のリストが含まれます。

Output
{ "acknowledged" : true, "insertedIds" : [ ObjectId("610c23828a94efbbf0cf6004"), ObjectId("610c23828a94efbbf0cf6005"), ObjectId("610c23828a94efbbf0cf6006"), ObjectId("610c23828a94efbbf0cf6007"), ObjectId("610c23828a94efbbf0cf6008") ] }

引数なしでfind()メソッドを実行すると、ドキュメントが正しく挿入されたことを確認できます。これにより、追加したばかりのすべてのドキュメントが取得されます。

  1. db.peaks.find()
Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } . . .

これで、クエリを作成するためのテストデータとして機能する山のサンプルドキュメントのリストが正常に作成されました。 次に、個々のフィールドを参照する条件でクエリを実行する方法を学習します。

ステップ2—個々のフィールドのクエリ

前の手順の最後に、MongoDBのfind()メソッドを使用して、peaksコレクションからすべてのドキュメントを返しました。 ただし、このようなクエリは、ドキュメントをフィルタリングせず、常に同じ結果セットを返すため、実際にはあまり役に立ちません。

結果セットに含めるためにドキュメントが順守する必要がある特定の条件を定義することにより、MongoDBでクエリ結果をフィルター処理できます。 MongoDB チュートリアルでCRUD操作を実行する方法に従っている場合は、最も基本的なフィルタリング条件である等式条件をすでに使用しています。

例として、nameの値がEverestと等しいドキュメントを返す次のクエリを実行します。

  1. db.peaks.find(
  2. { "name": "Everest" }
  3. )

2行目— { "name": "Everest" } —は、 query filter document です。これは、条件を満たすドキュメントを見つけるためにコレクションを検索するときに適用するフィルターを指定するJSONオブジェクトです。 この操作例は、name値が文字列Everestと一致するpeaksコレクション内のすべてのドキュメントを取得するようにMongoDBに指示します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

山が1つしかないため、MongoDBは単一のドキュメントを返しました。 peaksコレクションのエベレスト。

等式条件は、MongoDBがコレクション内のドキュメントと照合しようとする単一の値を指定します。 MongoDBは、比較クエリ演算子を提供します。これにより、単一のフィールドを参照する他の条件を指定できますが、完全一致を検索するよりも複雑な方法でドキュメントをフィルター処理できます。

比較演算子は、演算子自体、ドル記号($)が前に付いた単一のキー、およびクエリ演算子がドキュメントのフィルタリングに使用する値で構成されます。

説明のために、nameEverestと等しくないドキュメントを検索する次のクエリを実行します。

  1. db.peaks.find(
  2. { "name": { $ne: "Everest" } }
  3. )

今回は、クエリフィルタードキュメントに{ $ne: "Everest" }が含まれています。 $neはこの例の比較演算子であり、「等しくない」を表します。 ピーク名Everestが、この演算子の値として再び表示されます。 このクエリは、nameの値が等しくないEverestのドキュメントを検索しているため、次の4つのドキュメントが返されます。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } . . .

$in演算子を使用すると、配列に保持されている複数の値の1つに一致する値を持つドキュメントを返すクエリを記述できます。

次のクエリ例には、$in演算子が含まれており、nameの値がEverestまたはK2のいずれかに一致するドキュメントが返されます。

  1. db.peaks.find(
  2. { "name": { $in: ["Everest", "K2"] } }
  3. )

$in演算子に渡される値は、単一の値ではなく、中括弧で囲まれた2つのピーク名の配列です。 MongoDBは、期待どおりに2つのドキュメントを返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

これまでの例では、nameフィールドにテキスト値を照会しました。 数値に基づいてドキュメントをフィルタリングすることもできます。

次のクエリ例では、heightの値が8500より大きいドキュメントを検索します。

  1. db.peaks.find(
  2. { "height": { $gt: 8500 } }
  3. )

このクエリには、 greatthanを表す$gt演算子が含まれています。 値8500を渡すことにより、MongoDBはheight値が8500より大きいドキュメントを返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

MongoDBは、このセクションで概説されているものに加えて、いくつかの比較クエリ演算子を提供します。 これらの演算子の完全なリストについては、主題に関する公式ドキュメントを参照してください。

単一のドキュメントフィールドで等式条件と比較演算子を使用する方法がわかったので、1つのクエリで複数の条件を結合する方法の学習に進むことができます。

ステップ3—複数の条件を使用する

場合によっては、単一のドキュメントフィールドに基づくフィルタリングでは、対象のドキュメントを正確に選択するのに十分ではありません。 このような場合、一度に複数の条件を使用してドキュメントをフィルタリングすることができます。

MongoDBで複数の条件を接続する方法は2つあります。 1つ目は、論理AND論理積を使用して、コレクション内の all 条件に一致するドキュメントを選択するか、論理ORを使用して、リストから少なくとも1つの条件に一致するドキュメントを選択します。

MongoDBでは、クエリフィルタードキュメントで複数のフィールドを使用する場合、AND接続詞は暗黙的です。 エベレストという名前と正確な高さ8848メートルに一致する山を選択してみてください。

  1. db.peaks.find(
  2. { "name": "Everest", "height": 8848 }
  3. )

構文は前の手順の等式条件の例と似ていますが、今回は2つのフィールドがクエリフィルタードキュメントに表示されていることに注意してください。 MongoDBは両方のフィールドが等しいかどうかをチェックし、ドキュメントを選択するには、両方が要求された値と一致する必要があります。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

この場合、単一のドキュメントが返されますが、高さを他の数値に変更しようとすると、返されるドキュメントは両方の条件に一致する必要があるため、結果セットは空になります。 たとえば、次の例では、シェルに出力は返されません。

  1. db.peaks.find(
  2. { "name": "Everest", "height": 9000 }
  3. )

この暗黙のANDは、$and論理クエリ演算子の後に、返されたドキュメントが満たさなければならない条件のリストを含めることで明示的にすることができます。 次の例は、基本的に前のクエリと同じクエリですが、暗黙のAND接続詞の代わりに$and演算子が含まれています。

  1. db.peaks.find(
  2. { $and: [{"name": "Everest"}, {"height": 8848}] }
  3. )

今回は、$andクエリ演算子を含むJSONオブジェクトは、クエリフィルタードキュメント自体です。 ここで、比較演算子は、リストに表示される2つの別個の等式条件を取ります。1つはname一致の場合、もう1つはheight一致の場合です。

すべてではなく、選択した条件のいずれかに一致するドキュメントを選択するには、代わりに$or演算子を使用できます。

  1. db.peaks.find(
  2. { $or: [{"name": "Everest"}, {"name": "K2"}] }
  3. )

$or演算子を使用する場合、ドキュメントは2つの等式フィルターのいずれかを満たす必要があります。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

この例の各条件は単一フィールドの等式条件ですが、$and演算子と$or演算子の両方に、有効なクエリフィルタードキュメントを含めることができます。 ネストされたAND/OR条件リストを含めることもできます。

この手順で概説されているように、$andおよび$or演算子を使用して複数のフィルターを結合すると、きめ細かいクエリ結果を取得するのに非常に役立ちます。 ただし、これまでの例ではすべて、個々の値に基づいてフィルタリングするクエリフィルタドキュメントを使用しています。 次のステップでは、配列フィールドに格納されている値に対してクエリを実行する方法の概要を説明します。

ステップ4—配列値のクエリ

1つのフィールドに、配列に格納された複数の値が含まれる場合があります。 山頂の例では、locationがそのようなフィールドです。 ネパールやインドのカンチェンジュンガのように、山は複数の国にまたがることが多いため、この分野では単一の値で十分とは限りません。

このステップでは、配列フィールドの項目に一致するクエリフィルターを作成する方法を学習します。

ネパールにある山を表すドキュメントを選択することから始めましょう。 ただし、この例では、山の1つがネパールである限り、山に複数の場所がリストされていても問題ありません。

  1. db.peaks.find(
  2. { "location": "Nepal" }
  3. )

このクエリは、nameフィールドを使用した前の例と同様に、location値が指定された文字列値Nepalと完全に一致するドキュメントを返すようにMongoDBに指示する等式条件を使用します。 MongoDBは、要求された値が配列の任意の場所に表示されるすべてのドキュメントを選択します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

このクエリでは、MongoDBはlocationフィールドにネパール語が表示されている4つのドキュメントを返しました。

しかし、中国とネパールの両方にある山を見つけたい場合はどうでしょうか。 これを行うには、単一の値ではなく、フィルタードキュメントに配列を含めることができます。

  1. db.peaks.find(
  2. { "location": ["China", "Nepal"] }
  3. )

データベースにはネパールと中国に4つの山がありますが、このクエリで指定された順序で国がリストされているのは1つだけなので、このクエリは1つのドキュメントを返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

Makaluのlocationフィールドの値は、クエリのフィルタードキュメントと同じであることに注意してください。 このように等式条件の値として配列を指定すると、MongoDBは、locationフィールドが、配列内の要素の順序を含め、クエリフィルターと完全に一致するドキュメントを取得します。 説明のために、クエリを再度実行しますが、中国をネパールと交換します。

  1. db.peaks.find(
  2. { "location": ["Nepal", "China"] }
  3. )
Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

現在、他の2つの山が返されますが、マカルーは返されません。

このような等式条件を使用することは、完全一致ではなく、配列内の要素(順序に関係なく)のみを考慮する場合には役立ちません。 幸い、MongoDBでは、$allクエリ演算子を使用して、配列内の任意の場所に複数の配列要素を含むドキュメントを取得できます。

説明のために、次のクエリを実行します。

  1. db.peaks.find(
  2. { "location": { $all: ["China", "Nepal"] } }
  3. )

$allオペレーターは、location配列に中国とネパールの両方が任意の順序で含まれているかどうかを、ドキュメントがチェックされるようにします。 MongoDBは、1つのクエリで3つの山すべてを返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

この手順では、クエリフィルタドキュメントで配列を使用して、単一のフィールドに複数の値を持つドキュメントを取得する方法の概要を説明しました。 ネストされたドキュメント内に保持されているデータをクエリする場合は、そのような操作に必要な特別な構文を使用する必要があります。 これを行う方法を学ぶために次のステップに進んでください。

ステップ5—ネストされたドキュメントのフィールドをクエリする

サンプルのデータベースドキュメントには、各山の初登頂に関するさまざまな詳細を保持するascentフィールドが含まれていることを思い出してください。 このようにして、最初の登頂、冬の登頂、および登頂の総数に関するデータが、単一のネストされたドキュメント内にきれいにグループ化されます。 この手順では、クエリを作成するときにネストされたドキュメント内のフィールドにアクセスする方法について説明します。

サンプルEverestドキュメントをもう一度確認します。

エベレスト文書
{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

nameおよびheightフィールドへのアクセスは、これらのキーの下に単一の値が存在するため、簡単でした。 しかし、与えられたピークの上昇の総数を見つけたいとしましょう。 ascentsフィールドには、内部の上昇の総数だけでなく、より多くのデータが含まれています。 totalフィールドがありますが、メインドキュメントの一部ではないため、直接アクセスする方法はありません。

この問題を解決するために、MongoDBはネストされたドキュメントのフィールドにアクセスするためのドット表記を提供します。

MongoDBのドット表記がどのように機能するかを説明するには、次のクエリを実行します。 これにより、前に強調表示された$gt演算子を使用して、1000回以上上昇したコレクション内のすべての山が返されます。

  1. db.peaks.find(
  2. { "ascents.total": { $gt: 1000 } }
  3. )

山 エベレストはコレクション内で1000を超える登山がある唯一の山であるため、そのドキュメントのみが返されます。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

$gt演算子を使用した{ $gt: 1000 }クエリフィルターはおなじみですが、このクエリがascentsフィールドに格納されているドキュメント内に保持されているtotalフィールドにアクセスする方法に注意してください。 ネストされたドキュメントでは、任意のフィールドへのアクセスパスは、ネストされたオブジェクト内に入るアクションを示すドットで構成されます。

つまり、ascents.totalは、MongoDBが最初にascentsフィールドが指すネストされたドキュメントを開き、次にその中のtotalフィールドを見つける必要があることを意味します。

表記は、複数のネストされたドキュメントでも機能します。

  1. db.peaks.find(
  2. { "ascents.first_winter.year": { $gt: 2000 } }
  3. )

このクエリは、2000年以降にのみ冬に最初に登った山を説明するドキュメントを返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

以前と同様に、ascents.first_winter.year表記は、MongoDBが最初にascentsフィールドを検索し、そこにネストされたドキュメントを検索することを意味します。 次に、別のネストされたドキュメントfirst_winterに入り、最後にその中からyearフィールドを取得します。

ドット表記を使用して、MongoDB内のネストされたドキュメントの任意の深さにアクセスできます。

これで、ネストされたドキュメントのデータにアクセスする方法と、クエリ結果をフィルタリングする方法を十分に理解できるようになります。 クエリによって返されるフィールドのリストを制限する方法の学習に進むことができます。

ステップ6—フィールドのサブセットを返す

これまでのすべての例で、peaksコレクションをクエリすると、MongoDBは1つ以上の完全なドキュメントを返しました。 多くの場合、必要なのはほんの一握りのフィールドからの情報だけです。 例として、データベースで山の名前だけを検索したい場合があります。

これは読みやすさだけでなく、パフォーマンスの問題でもあります。 ドキュメントのごく一部のみが必要な場合、ドキュメントオブジェクト全体を取得すると、データベースのパフォーマンスが不必要に低下します。 これは、このチュートリアルの例のように小さなデータセットを操作する場合は問題にならないかもしれませんが、多くの大きくて複雑なドキュメントを操作する場合は重要な考慮事項になります。

例として、peaksコレクションに保存されている山の名前だけに関心があるとしますが、今回は登りの詳細や場所は重要ではありません。 Projection を使用してクエリフィルタードキュメントを追跡することにより、クエリが返すフィールドを制限できます。

プロジェクションドキュメントは、キーがクエリされたドキュメントのフィールドに対応するJSONオブジェクトです。 投影は、包含投影または除外投影のいずれかとして構築できます。 投影ドキュメントに1が値として含まれるキーが含まれている場合、結果に含まれるフィールドのリストが記述されます。 一方、投影キーが0に設定されている場合、投影ドキュメントには、結果から除外されるフィールドのリストが記述されています。

次のクエリを実行します。これには、今ではおなじみのfind()メソッドが含まれています。 このクエリのfind()メソッドには、1つではなく2つの引数が含まれています。 最初の{}は、クエリフィルタードキュメントです。 これは空のJSONオブジェクトです。つまり、フィルタリングは適用されません。 2番目の引数{ "name": 1 }は、射影を記述し、クエリ結果に各ドキュメントのnameフィールドのみが含まれることを意味します。

  1. db.peaks.find(
  2. {},
  3. { "name": 1 }
  4. )

このサンプルクエリを実行した後、MongoDBは次の結果を返します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest" } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2" } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga" } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse" } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu" }

返されるドキュメントは簡略化されており、nameフィールドと_idフィールドのみが含まれていることに注意してください。 MongoDBには、明示的に要求されていない場合でも、常に_idキーが含まれています。

除外するフィールドを指定する方法を説明するには、次のクエリを実行します。 各ドキュメントからデータを返しますが、ascentsおよびlocationフィールドは除外されます。

  1. db.peaks.find(
  2. {},
  3. { "ascents": 0, "location": 0 }
  4. )

MongoDBは、5つの山すべてをもう一度返しますが、今回はnameheight、および_idフィールドのみが存在します。

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848 } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611 } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586 } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516 } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485 }

注:プロジェクションを指定する場合、包含と除外を混在させることはできません。 含めるフィールドのリスト、または除外するフィールドのリストのいずれかを指定する必要があります。

ただし、この規則には1つの例外があります。 MongoDBを使用すると、クエリに包含プロジェクションが適用されている場合でも、結果セットから_idフィールドを除外できます。 _idフィールドを非表示にするには、"_id": 0をプロジェクションドキュメントに追加します。 次の例は前のクエリ例と似ていますが、nameフィールドを除いて、_idを含むすべてのフィールドを除外します。

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1 }
  4. )
Output
{ "name" : "Everest" } { "name" : "K2" } { "name" : "Kangchenjunga" } { "name" : "Lhotse" } { "name" : "Makalu" }

射影を使用して、ネストされたドキュメントのフィールドを含めたり除外したりすることもできます。 たとえば、各山の最初の冬の登りと登りの総数を知りたいとします。どちらもascentsフィールド内にネストされています。 さらに、各山の名前を返したいと思います。 これを行うには、次のようなクエリを実行できます。

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "ascents": { "first_winter": 1, "total": 1 } }
  4. )

ascentsフィールドにプロジェクションがどのように指定されているか、およびネストされたドキュメントの構造に従って、ネストされたプロジェクション自体であることに注意してください。 "first_winter": 1, "total": 1を使用することにより、このクエリは、ネストされたドキュメントのこれら2つのフィールドのみを含め、他のフィールドは含めないようにデータベースに指示します。

返されるドキュメントには、要求されたフィールドのみが含まれます。

Output
{ "name" : "Everest", "ascents" : { "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "name" : "K2", "ascents" : { "first_winter" : { "year" : 1921 }, "total" : 306 } } { "name" : "Kangchenjunga", "ascents" : { "first_winter" : { "year" : 1988 }, "total" : 461 } } { "name" : "Makalu", "ascents" : { "first_winter" : { "year" : 2009 }, "total" : 361 } }

返されるドキュメントのサイズをフィールドのサブセットのみに制限すると、結果セットが読みやすくなり、パフォーマンスが向上する場合もあります。 次の手順では、クエリによって返されるドキュメントの数を制限する方法の概要と、クエリによって返されるデータを並べ替える方法について詳しく説明します。

ステップ7—カーソルを使用してクエリ結果を並べ替えて制限する

大規模なコレクションからオブジェクトを取得する場合、結果の数を制限したり、特定の順序で並べ替えたりすることがあります。 たとえば、ショッピングサイトで人気のあるアプローチは、商品を価格で並べ替えることです。 MongoDBはcursorsを使用します。これにより、クエリ結果セットで返されるドキュメントの数を制限したり、結果を昇順または降順で並べ替えたりすることができます。

手順1のこのクエリ例を思い出してください。

  1. db.peaks.find()

このクエリによって返される結果セットには、peaksコレクション内の各ドキュメントのすべてのデータが含まれていることを思い出してください。 MongoDBはpeaksコレクションからすべてのオブジェクトを返すように見えるかもしれませんが、そうではありません。 MongoDBが実際に返すのは、cursorオブジェクトです。

カーソルはクエリの結果セットへのポインタですが、結果セット自体ではありません。 これは反復可能なオブジェクトです。つまり、カーソルにの次のドキュメントを返すように要求できます。そうすると、データベースからドキュメント全体が取得されます。 それが発生するまで、カーソルはリストの次のドキュメントのみを指します。

カーソルを使用すると、MongoDBは、実際のドキュメント検索が必要な場合にのみ行われるようにすることができます。 これは、問題のドキュメントが大きい場合、またはそれらの多くが一度に要求される場合に、パフォーマンスに重大な影響を与える可能性があります。

カーソルがどのように機能するかを説明するために、find()メソッドとcount()メソッドの両方を含む次の操作を実行します。

  1. db.peaks.find().count()

MongoDBは5で応答します:

Output
5

内部的には、find()メソッドがカーソルを見つけて返し、そのカーソルでcount()メソッドが呼び出されます。 これにより、MongoDBは、ドキュメント自体ではなく、オブジェクト数に関心があることを認識できます。 これは、ドキュメントが結果の一部にならないことを意味します—データベースが返すのはカウントだけです。 カーソルオブジェクトのメソッドを使用して、カーソルからドキュメントを取得する前にクエリをさらに変更すると、要求したデータベース操作のみがコレクションに対して実行されるようにすることができます。

注:クエリを実行すると、MongoDBシェルは返されたカーソルを自動的に20回繰り返し、最初の20件の結果を画面に表示します。 これはMongoDBシェルに固有です。 プログラムでMongoDBを操作する場合、カーソルから結果をすぐに取得することはありません。

カーソルを使用して結果セットを変更するもう1つのMongoDBメソッドは、limit()メソッドです。 その名前が示すように、limit()を使用して、クエリが返す結果の数を制限できます。

コレクションから3つの山頂のみを取得する次のクエリを実行します。

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "height": 1 }
  4. ).limit(3)

クエリがデータをフィルタリングしていない場合でも、MongoDBシェルは5つではなく3つのオブジェクトで応答します。

Output
{ "name" : "Everest", "height" : 8848 } { "name" : "K2", "height" : 8611 } { "name" : "Kangchenjunga", "height" : 8586 }

カーソルに適用されるlimit(3)メソッドは、最初の3に達した後、それ以上のドキュメントを返すのを停止するようにカーソルに指示します。 大規模なコレクションでこのようなlimit()カーソル方式を使用すると、必要な結果のみを取得し、それ以上取得しないようにするのに役立ちます。

デフォルトでは、MongoDBは挿入順にオブジェクトを返しますが、その動作を変更したい場合があります。 データベースに保持されている3つの最も低い山のピークを見つけることに興味があるとします。 次のクエリを実行できます。

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "height": 1 }
  4. ).limit(3).sort({ "height": 1 })

追加されたsort({ "height": 1 })により、結果セットは前の例とは異なります。

Output
{ "name" : "Makalu", "height" : 8485 } { "name" : "Lhotse", "height" : 8516 } { "name" : "Kangchenjunga", "height" : 8586 }

ここでも、3つの山頂のみが返されます。 ただし、今回はheightの値が最も低いものから昇順に並べ替えられています。

カーソルのsort()メソッドは、プロジェクションドキュメントと同様に、JSONオブジェクト(height)を引数として受け入れます。 また、並べ替えに使用されるキーのリストも受け入れます。 受け入れられる値は、各キーの昇順の場合は1、降順の場合は-1のいずれかです。

結論

この記事を読むことで、MongoDBがクエリ結果をフィルタリングするために使用する方法を理解できました。 個々のフィールド、複数の条件、および配列やネストされたドキュメントなどの複雑な構造に対してコレクションドキュメントをフィルタリングしました。 また、フィールドのサブセットのみを選択し、カーソルメソッドを使用して結果を並べ替えることも学習しました。 これらの手法を使用して、他の方法では大規模なコレクションから関心のあるドキュメントのみを取得できます。

チュートリアルでは、正確なドキュメントクエリを可能にするために、MongoDBによって提案されたほんの一握りのクエリ演算子について説明しました。 公式の公式のMongoDBドキュメントを調べて、さまざまなクエリ演算子について詳しく知ることができます。