MongoDB のクエリ - Node.js SDK
項目一覧
Realm Node.js SDK の MongoDB クライアントとQuery APIを使用すると、MongoDB Atlas に保存されているデータをクライアント アプリケーション コードから直接クエリできます。 Atlas App Servicesは、ログインしたユーザーまたは各ドキュメントの内容に基づいて結果を安全に取得するためのコレクションにデータ アクセスルールを提供します。
注意
サンプル データセット
このページの例では、植物店舗のチェーンの在庫を説明する MongoDB コレクションを使用しています。 コレクション スキーマとドキュメントの内容の詳細については、「サンプル データ 」を参照してください。
ユースケース
MongoDB データソースをクエリする理由はさまざまあります。 Atlas Device Sync を介してクライアント内のデータを操作することは、必ずしも現実的ではないか、可能な限りありません。 次の場合には、MongoDB をクエリすることをお勧めします。
データセットが大きいか、クライアント デバイスにデータセット全体のロードに対する制約がある
Realm でモデル化されていないドキュメントを取得している
アプリは厳密なスキーマを持たないコレクションにアクセスする必要があります
非 Realm サービスは、アクセスしたいコレクションを生成します。
網羅的なものではありませんが、これらは MongoDB を直接クエリする一般的なユースケースです。
前提条件
Node.js アプリケーションから MongoDB をクエリする前に、App Services App で MongoDB データ アクセス を設定する必要があります。 バックエンド アプリを設定して Realm SDK クエリ Atlas を使用できるようにする方法については、App Services ドキュメントの「 MongoDB データアクセスの設定 」を参照してください。
例
サンプルデータ
このページの例では、植物店のチェーンで販売されるさまざまな植物を説明する次の MongoDB コレクションを使用します。
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c5"), name: "sweet basil", sunlight: "partial", color: "green", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c7"), name: "helianthus", sunlight: "full", color: "yellow", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c8"), name: "petunia", sunlight: "full", color: "purple", type: "annual", _partition: "Store 47" }
plants
コレクション内のドキュメントは次のスキーマを使用します。
{ "title": "Plant", "bsonType": "object", "required": ["_id", "_partition", "name"], "properties": { "_id": { "bsonType": "objectId" }, "_partition": { "bsonType": "string" }, "name": { "bsonType": "string" }, "sunlight": { "bsonType": "string" }, "color": { "bsonType": "string" }, "type": { "bsonType": "string" } } }
type Plant = { _id: BSON.ObjectId; _partition: string; name: string; sunlight?: string; color?: string; type?: string; };
連結クラスターへの接続
クライアント アプリケーションからリンクされたクラスターにアクセスするには、クラスター名をユーザー.mongoClient()に渡します。 これにより、クラスター内のデータベースとコレクションにアクセスするために使用できる MongoDB サービス インターフェースが返されます。
const mongodb = app.currentUser.mongoClient("mongodb-atlas"); const plants = mongodb.db("example").collection("plants");
const mongodb = app.currentUser.mongoClient("mongodb-atlas"); const plants = mongodb.db("example").collection<Plant>("plants");
読み取り操作
単一ドキュメントの検索
単一ドキュメントを検索するには、そのドキュメントに一致するクエリをcollection.findOne()に渡します。 クエリを渡さない場合、 findOne()
はコレクション内で最初に見つけたドキュメントと一致します。
次のスニペットは、店舗のグループで販売されるプランを説明するドキュメントの コレクション内で、「venus f Collection」プランを説明するドキュメントを検索します。
const venusFlytrap = await plants.findOne({ name: "venus flytrap" }); console.log("venusFlytrap", venusFlytrap);
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42", }
複数ドキュメントの検索
複数のドキュメントを検索するには、ドキュメントに一致するクエリをcollection.find()に渡します。 クエリを渡さない場合、 find()
はコレクション内のすべてのドキュメントを照合します。
次のスニペットは、店舗のグループで販売されるプランを説明するドキュメントの コレクション内の 1 月ごとのプランを説明するすべてのドキュメントを検索します。
const perennials = await plants.find({ type: "perennial" }); console.log("perennials", perennials);
[ { _id: ObjectId("5f87976b7b800b285345a8c4"), name: 'venus flytrap', sunlight: 'full', color: 'white', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: 'thai basil', sunlight: 'partial', color: 'green', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f879f83fc9013565c23360e"), name: 'lily of the valley', sunlight: 'full', color: 'white', type: 'perennial', _partition: 'Store 47' }, { _id: ObjectId("5f87a0defc9013565c233611"), name: 'rhubarb', sunlight: 'full', color: 'red', type: 'perennial', _partition: 'Store 47' }, { _id: ObjectId("5f87a0dffc9013565c233612"), name: 'wisteria lilac', sunlight: 'partial', color: 'purple', type: 'perennial', _partition: 'Store 42' }, { _id: ObjectId("5f87a0dffc9013565c233613"), name: 'daffodil', sunlight: 'full', color: 'yellow', type: 'perennial', _partition: 'Store 42' } ]
ドキュメントをカウント
ドキュメントをカウントするには、そのドキュメントに一致するクエリをcollection.count()に渡します。 クエリを渡さない場合、 count()
はコレクション内のすべてのドキュメントをカウントします。
次のスニペットは、店舗のグループで販売されるプランを説明するドキュメントのコレクション内のドキュメントの数をカウントします。
const numPlants = await plants.count(); console.log(`There are ${numPlants} plants in the collection`);
"There are 9 plants in the collection"
書込み操作
単一ドキュメントのインサート
単一ドキュメントを挿入するには、それをcollection.insertOne() に渡します。
次のスニペットは、「 Ops Manager 」の植物を説明する単一のドキュメントを、店舗のグループで販売するプランを説明するドキュメントのコレクションに挿入します。
const result = await plants.insertOne({ name: "lily of the valley", sunlight: "full", color: "white", type: "perennial", _partition: "Store 47", }); console.log(result);
{ insertedId: "5f879f83fc9013565c23360e", }
複数のドキュメントの挿入
複数のドキュメントを同時に挿入するには、それらを配列としてcollection.insertMany() に渡します。
次のスニペットは、プランを説明する 3 つのドキュメントを、店舗のグループで販売するプランを説明するドキュメントのコレクションに挿入します。
const result = await plants.insertMany([ { name: "rhubarb", sunlight: "full", color: "red", type: "perennial", _partition: "Store 47", }, { name: "wisteria lilac", sunlight: "partial", color: "purple", type: "perennial", _partition: "Store 42", }, { name: "daffodil", sunlight: "full", color: "yellow", type: "perennial", _partition: "Store 42", }, ]); console.log(result);
{ insertedIds: [ "5f87a0defc9013565c233611", "5f87a0dffc9013565c233612", "5f87a0dffc9013565c233613", ], }
単一ドキュメントの更新
単一のドキュメントを更新するには、そのドキュメントに一致するクエリと更新ドキュメントをcollection.updateOne() に渡します。
次のスニペットは、 店舗 のグループで販売されるプランを説明するドキュメントのコレクション内の 1 つのドキュメントを更新します。 この操作は、 name
フィールドに値「ペナルティ」が含まれているドキュメントをクエリし、最初に一致したドキュメントのsunlight
フィールドの値を「部分的」に変更します。
const result = await plants.updateOne( { name: "petunia" }, { $set: { sunlight: "partial" } } ); console.log(result);
{ matchedCount: 1, modifiedCount: 1 }
複数のドキュメントの更新
複数のドキュメントを同時に更新するには、ドキュメントに一致するクエリと更新の説明をcollection.updateMany() に渡します。
次のスニペットは、 店舗 のグループで販売されるプランを説明するドキュメントのコレクション内の複数のドキュメントを更新します。 この操作は、 _partition
フィールドに値「 47が含まれているドキュメントをクエリし、一致する各ドキュメントの_partition
フィールドの値を「 51を保存」に変更します。
const result = await plants.updateMany( { _partition: "Store 47" }, { $set: { _partition: "Store 51" } } ); console.log(result);
{ matchedCount: 3, modifiedCount: 3 }
ドキュメントをアップサートする
ドキュメントをアップサートするには、更新操作でupsert
オプションをtrue
に設定します。 操作のクエリがコレクション内のどのドキュメントにも一致しない場合、アップサートによって、指定されたクエリドキュメントと一致する新しいドキュメントがコレクションに自動的に挿入されます。
次のスニペットは、アップサート操作を使用して、店舗のグループで販売するプランを説明するドキュメントのコレクション内のドキュメントを更新します。 クエリは既存のドキュメントと一致しないため、MongoDB は自動的に新しいドキュメントを作成します。
const result = await plants.updateOne( { sunlight: "full", type: "perennial", color: "green", _partition: "Store 47", }, { $set: { name: "sweet basil" } }, { upsert: true } ); console.log(result);
{ matchedCount: 0, modifiedCount: 0, upsertedId: ObjectId("5f1f63055512f2cb67f460a3"), }
単一ドキュメントの削除
コレクションから単一ドキュメントを削除するには、そのドキュメントに一致するクエリをcollection.deleteOne()に渡します。 クエリを渡されない場合、またはクエリが複数のドキュメントに一致する場合、操作は最初に見つけたドキュメントを削除します。
次のスニペットは、 店舗のグループで販売されるプランを説明するドキュメントのコレクション内の 1 つのドキュメントを削除します。 この操作は、 color
フィールドの値が「green」であるドキュメントをクエリし、クエリに一致する最初のドキュメントを削除します。
const result = await plants.deleteOne({ color: "green" }); console.log(result);
{ deletedCount: 1 }
複数のドキュメントの削除
コレクションから複数のドキュメントを削除するには、ドキュメントに一致するクエリをcollection.deleteMany()に渡します。 クエリを渡さない場合、 deleteMany()
はコレクション内のすべてのドキュメントを削除します。
次のスニペットは、店舗のグループで販売されるプランを説明するドキュメントのコレクション内の " Store 51にあるプラン照のすべてのドキュメントを削除します。
const result = await plants.deleteMany({ _partition: "Store 51", }); console.log(result);
{ deletedCount: 3 }
リアルタイム変更通知
collection.watch()を呼び出して、コレクション内のドキュメントが追加、変更、または削除されるたびに MongoDB が発行するリアルタイム変更通知をサブスクライブできます。 各通知では、変更されたドキュメントとその変更方法、およびイベントの原因となった操作後の完全なドキュメントを指定します。
注意
collection.watch()
は 非同期ジェネレーター を返します により、操作の 変更イベント が発生したときに非同期にプルできます。
重要
サーバーレスの制限事項
データソースが Atlas サーバーレスインスタンスの場合、変更を監視することはできません。 MongoDB サーバーレスは現在、変更をリッスンするために監視対象コレクションで使用される 変更ストリーム をサポートしていません。
コレクション内のすべての変更の監視
コレクション内のすべての変更を監視するには、引数なしでcollection.watch()を呼び出します。
for await (const change of plants.watch()) { switch (change.operationType) { case "insert": { const { documentKey, fullDocument } = change; console.log(`new document: ${documentKey}`, fullDocument); break; } case "update": { const { documentKey, fullDocument } = change; console.log(`updated document: ${documentKey}`, fullDocument); break; } case "replace": { const { documentKey, fullDocument } = change; console.log(`replaced document: ${documentKey}`, fullDocument); break; } case "delete": { const { documentKey } = change; console.log(`deleted document: ${documentKey}`); break; } } }
コレクション内の特定の変更の監視
コレクション内の特定の変更を監視するには、変更イベントフィールドに一致するクエリをcollection.watch()に渡します。
for await (const change of plants.watch({ filter: { operationType: "insert", "fullDocument.type": "perennial", }, })) { // The change event will always represent a newly inserted perennial const { documentKey, fullDocument } = change; console.log(`new document: ${documentKey}`, fullDocument); }
集計操作
集計操作は、集計パイプラインと呼ばれる一連のステージを通じてコレクション内のすべてのドキュメントを実行します。 集計を使用すると、ドキュメントのフィルタリングと変換、関連するドキュメントのグループに関するサマリー データの収集、その他の複雑なデータ操作が可能になります。
集計パイプラインの実行
集計パイプラインを実行するには、集計ステージの配列をcollection.aggregate()に渡します。 集計操作は、パイプラインの最後のステージの結果セットを返します。
次のスニペットは、 plants
コレクション内のすべてのドキュメントをtype
値でグループ化し、各タイプの数を集計します。
const result = await plants.aggregate([ { $group: { _id: "$type", total: { $sum: 1 }, }, }, { $sort: { _id: 1 } }, ]); console.log(result);
[ { _id: "annual", total: 1 }, { _id: "perennial", total: 5 }, ]
フィルター ドキュメント
$matchステージを使用して、標準の MongoDBクエリ構文に従ってドキュメントをフィルタリングできます。
{ "$match": { "<Field Name>": <Query Expression>, ... } }
例
次の$match
ステージでは、ドキュメントをフィルタリングして、 type
フィールドの値が「perennial」と等しいドキュメントのみを含めます。
const perennials = await plants.aggregate([ { $match: { type: { $eq: "perennial" } } }, ]); console.log(perennials);
[ { "_id": ObjectId("5f87976b7b800b285345a8c4"), "_partition": "Store 42", "color": "white", "name": "venus flytrap", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c6"), "_partition": "Store 42", "color": "green", "name": "thai basil", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233612"), "_partition": "Store 42", "color": "purple", "name": "wisteria lilac", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233613"), "_partition": "Store 42", "color": "yellow", "name": "daffodil", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f1f63055512f2cb67f460a3"), "_partition": "Store 47", "color": "green", "name": "sweet basil", "sunlight": "full", "type": "perennial" } ]
グループ ドキュメント
$groupステージを使用して、1 つ以上のドキュメントのサマリーデータを集計できます。 MongoDB は、 $group
ステージの_id
フィールドに定義された式に基づいてドキュメントをグループ化します。 フィールド名の前に$
を付けることで、特定のドキュメント フィールドを参照できます。
{ "$group": { "_id": <Group By Expression>, "<Field Name>": <Aggregation Expression>, ... } }
例
次の$group
ステージでは、ドキュメントをtype
type
フィールドの値で配置し、一意の 各 値が表示される植物ドキュメントの数を計算します。
const result = await plants.aggregate([ { $group: { _id: "$type", numItems: { $sum: 1 }, }, }, { $sort: { _id: 1 } }, ]); console.log(result);
[ { _id: "annual", numItems: 1 }, { _id: "perennial", numItems: 5 }, ]
ドキュメントのページ分割
結果をページ分割するには、 $match
、 $sort
、 $limit
演算子を使用した範囲集計クエリを使用できます。 ドキュメントのページ分割の詳細については、 ドキュメントの「 範囲クエリの使用 MongoDB Server」を参照してください。
例
次の例では、ドキュメントのコレクションを昇順にページ分割します。
// Paginates through list of plants // in ascending order by plant name (A -> Z) async function paginateCollectionAscending( collection, nPerPage, startValue ) { const pipeline = [{ $sort: { name: 1 } }, { $limit: nPerPage }]; // If not starting from the beginning of the collection, // only match documents greater than the previous greatest value. if (startValue !== undefined) { pipeline.unshift({ $match: { name: { $gt: startValue }, }, }); } const results = await collection.aggregate(pipeline); return results; } // Number of results to show on each page const resultsPerPage = 3; const pageOneResults = await paginateCollectionAscending( plants, resultsPerPage ); const pageTwoStartValue = pageOneResults[pageOneResults.length - 1].name; const pageTwoResults = await paginateCollectionAscending( plants, resultsPerPage, pageTwoStartValue ); // ... can keep paginating for as many plants as there are in the collection
プロジェクト ドキュメント フィールド
$projectステージを使用して、ドキュメントから特定のフィールドを含めたり省略したり、集計演算子を使用して新しいフィールドを計算したりできます。 プロジェクションは、次の 2 つの方法で機能します。
値により 1 のフィールドが明示的に含められます。 これには、指定されていないすべてのフィールドを暗黙的に除外するという副作用があります。
値が 0 のフィールドを暗黙的に除外します。これには、指定されていないすべてのフィールドが暗黙的に含まれるという副作用があります。
これらの 2 つのプロジェクション方法は相互に排他的です。つまり、フィールドを明示的に含める場合は、フィールドを明示的に除外することはできません。また、その逆も同様です。
注意
_id
フィールドは特別なケースで、明示的に指定されない限り、すべてのクエリに常に含まれます。 このため、 0
値を持つ_id
フィールドを除外しながら、同時に_partition
などの他のフィールドを1
とともに含めることができます。 _id
フィールドを除外するという特別なケースのみ、1 つの$project
ステージで除外と包含の両方が許可されます。
{ "$project": { "<Field Name>": <0 | 1 | Expression>, ... } }
例
次の$project
ステージでは、 _id
フィールドが省略され、 name
フィールドが含まれ、 storeNumber
という名前の新しいフィールドが作成されます。 storeNumber
は 2 つの集計演算子を使用して生成されます。
$split
は、_partition
値をスペース文字で囲む 2 つのstringセグメントに分割します。 たとえば、この方法で分割された値「Store 42」は、「store」と「42」の 2 つの要素を含む配列を返します。$arrayElemAt
は、2 番目の引数に基づいて配列から特定の要素を選択します。 この場合、値1
は、0
の配列インデックス以降、$split
演算子によって生成された配列から 2 番目の要素を選択します。 たとえば、この操作に渡される値 ["store", "42"] は "42" の値を返します。
const result = await plants.aggregate([ { $project: { _id: 0, name: 1, storeNumber: { $arrayElemAt: [{ $split: ["$_partition", " "] }, 1], }, }, }, ]); console.log(result);
[ { "name": "venus flytrap", "storeNumber": "42" }, { "name": "thai basil", "storeNumber": "42" }, { "name": "helianthus", "storeNumber": "42" }, { "name": "wisteria lilac", "storeNumber": "42" }, { "name": "daffodil", "storeNumber": "42" }, { "name": "sweet basil", "storeNumber": "47" } ]
ドキュメントへのフィールドの追加
$addFieldsステージを使用して、集計演算子を使用して計算された値を持つ新しいフィールドを追加できます。
{ $addFields: { <newField>: <expression>, ... } }
注意
$addFields
は$projectに似ていますが、フィールドを含めたり省略したりすることはできません。
例
次の$addFields
ステージでは、 storeNumber
という名前の新しいフィールドが作成されます。値は_partition
フィールドの値を変換する 2 つの集計演算子の出力です。
const result = await plants.aggregate([ { $addFields: { storeNumber: { $arrayElemAt: [{ $split: ["$_partition", " "] }, 1], }, }, }, ]); console.log(result);
[ { "_id": ObjectId("5f87976b7b800b285345a8c4"), "_partition": "Store 42", "color": "white", "name": "venus flytrap", "storeNumber": "42", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c6"), "_partition": "Store 42", "color": "green", "name": "thai basil", "storeNumber": "42", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87976b7b800b285345a8c7"), "_partition": "Store 42", "color": "yellow", "name": "helianthus", "storeNumber": "42", "sunlight": "full", "type": "annual" }, { "_id": ObjectId("5f87a0dffc9013565c233612"), "_partition": "Store 42", "color": "purple", "name": "wisteria lilac", "storeNumber": "42", "sunlight": "partial", "type": "perennial" }, { "_id": ObjectId("5f87a0dffc9013565c233613"), "_partition": "Store 42", "color": "yellow", "name": "daffodil", "storeNumber": "42", "sunlight": "full", "type": "perennial" }, { "_id": ObjectId("5f1f63055512f2cb67f460a3"), "_partition": "Store 47", "color": "green", "name": "sweet basil", "storeNumber": "47", "sunlight": "full", "type": "perennial" } ]
Unwind Array Values
$unwindステージを使用して、配列を含む単一のドキュメントを、その配列の個々の値を含む複数のドキュメントに変換できます。 配列フィールドを展開すると、MongoDB は配列フィールドの要素ごとに各ドキュメントを 1 回コピーしますが、コピーごとに配列値を配列要素に置き換えます。
{ $unwind: { path: <Array Field Path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
例
次の例では、各オブジェクトのtype
とcolor
の組み合わせに対して$unwind
ステージを使用します。 集計パイプラインには、次の手順があります。
$group
ステージを$addToSet
と併用して、コレクション内に発生するその花の種類のすべての色の配列を含む新しいフィールドcolors
を持つ各type
に新しいドキュメントを作成します。$unwind
ステージを使用して、タイプと色の組み合わせごとに個別のドキュメントを作成します。$sort
ステージを使用して、結果をアルファベット順にソートします。
const result = await plants.aggregate([ { $group: { _id: "$type", colors: { $addToSet: "$color" } } }, { $unwind: { path: "$colors" } }, { $sort: { _id: 1, colors: 1 } }, ]); console.log(result);
[ { "_id": "annual", "colors": "yellow" }, { "_id": "perennial", "colors": "green" }, { "_id": "perennial", "colors": "purple" }, { "_id": "perennial", "colors": "white" }, { "_id": "perennial", "colors": "yellow" }, ]