Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

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 が発行するリアルタイム変更通知をサブスクライブできます。 各通知では、変更されたドキュメントとその変更方法、およびイベントの原因となった操作後の完全なドキュメントを指定します。

注意

重要

サーバーレスの制限事項

データソースが 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 つの集計演算子を使用して生成されます。

  1. $split は、_partition 値をスペース文字で囲む 2 つのstringセグメントに分割します。 たとえば、この方法で分割された値「Store 42」は、「store」と「42」の 2 つの要素を含む配列を返します。

  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ステージを使用して、配列を含む単一のドキュメントを、その配列の個々の値を含む複数のドキュメントに変換できます。 配列フィールドを展開すると、MongoDB は配列フィールドの要素ごとに各ドキュメントを 1 回コピーしますが、コピーごとに配列値を配列要素に置き換えます。

{
$unwind: {
path: <Array Field Path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}

次の例では、各オブジェクトのtypecolorの組み合わせに対して$unwindステージを使用します。 集計パイプラインには、次の手順があります。

  1. $groupステージを$addToSetと併用して、コレクション内に発生するその花の種類のすべての色の配列を含む新しいフィールドcolorsを持つ各typeに新しいドキュメントを作成します。

  2. $unwindステージを使用して、タイプと色の組み合わせごとに個別のドキュメントを作成します。

  3. $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" },
]

戻る

関数の呼び出し