查询 MongoDB — Web SDK
在此页面上
您可以通过使用Realm Web SDK 的 MongoDB客户端和Query API直接从客户端应用程序代码查询存储在MongoDB Atlas中的数据。 Atlas App Services提供集合的数据访问规则,以便根据登录用户或每个文档的内容安全地检索结果。
通过以下操作,您可以使用 Realm Web SDK 从 Web 应用程序查询关联的 MongoDB Atlas 集群。
注意
本页描述的每个操作都使用查询来匹配操作执行所依据的集合中的某些文档。 当筛选器匹配集合中的多个文档时,除非指定排序参数,否则将以不确定的顺序返回这些文档。 这意味着,如果您没有为 、 findOne()
、 updateOne()
或deleteOne()
函数指定排序,您的操作可能会匹配与查询匹配的任何文档。 有关排序的更多信息,请参阅cursor.sort()。
先决条件
从 Web 应用程序查询 MongoDB 之前,必须在 App Services App 中设置 MongoDB 数据访问权限。 要了解如何设置后端应用程序以使Realm SDK 查询Atlas ,请参阅 MongoDBAtlas App Services文档中的 设置 数据访问权限 。
设置您的项目
设置您的项目
按照安装 Realm for Web 指南中的步骤进行操作。
链接 MongoDB Atlas 服务集群
请按照链接数据源指南中的步骤操作。为服务指定有意义的名称 — 您在使用 Realm SDK 连接集群时需要用到它。
示例数据
本页上的示例使用以下 MongoDB 集合,而该集合描述了连锁植物商店中待售的各种植物:
{ _id: ObjectId("5f87976b7b800b285345a8b4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42", }, { _id: ObjectId("5f87976b7b800b285345a8b5"), name: "sweet basil", sunlight: "partial", color: "green", type: "annual", _partition: "Store 42", }, { _id: ObjectId("5f87976b7b800b285345a8b6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42", }, { _id: ObjectId("5f87976b7b800b285345a8b7"), name: "helianthus", sunlight: "full", color: "yellow", type: "annual", _partition: "Store 42", }, { _id: ObjectId("5f87976b7b800b285345a8b8"), name: "petunia", sunlight: "full", color: "purple", type: "annual", _partition: "Store 47", },
创建文档
这些代码片段演示了如何从 Web应用将一个或多个文档插入MongoDB集合。插入操作采用要添加到MongoDB的一个或多个文档作为参数,并返回 Promise 解析为包含操作执行结果的对象。
插入单一文档
您可以通过调用集合.insertOne()来插入单个文档。
以下代码片段会将描述“铃兰”植物的单个文档插入到描述一组商店中待售植物的文档集合中:
const result = await plants.insertOne({ name: "lily of the valley", sunlight: "full", color: "white", type: "perennial", _partition: "Store 47", }); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ insertedId: ObjectId("5f879f83fc9013565c23360e") }
插入多个文档
您可以使用collection.insertMany() 同时插入多个文档。
以下代码段将三个描述植物的文档插入到描述一组商店中待售植物的文档集合中:
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: [ ObjectId("5f87a0defc9013565c233611"), ObjectId("5f87a0defc9013565c233612"), ObjectId("5f87a0defc9013565c233613"), ], }
读取文档
这些代码片段演示了如何从移动应用程序读取存储在MongoDB集合中的数据。 读取操作使用查询筛选器来指定要从数据库返回哪些文档。 读取操作会返回 Promise 解析为以下内容之一:单个匹配的文档(在findOne()
的情况下)、数值(在count()
的情况下)或匹配文档的大量(在find()
的情况下)。
查找单个文档
您可以使用collection.findOne() 查找单个文档。
以下代码片段在描述一组商店中待售植物的文档集合中查找描述捕蝇草的文档:
const venusFlytrap = await plants.findOne({ name: "venus flytrap" }); console.log("venusFlytrap", venusFlytrap);
运行此代码段将生成类似于以下内容的输出:
{ _id: ObjectId("5f87976b7b800b285345a8b4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42", }
查找多个文档
您可以使用collection.find() 查找多个文档。
以下代码段查找描述一组商店中待售植物的文档集合中的所有文档,其中包含名为type
且值为“perennial”的字段:
const perennials = await plants.find({ type: "perennial" }); console.log("perennials", perennials);
运行此代码段将生成类似于以下内容的输出:
[ { _id: ObjectId("5f87976b7b800b285345a8b4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42", }, { _id: ObjectId("5f87976b7b800b285345a8b6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42", }, ]
对集合中的文档进行计数
您可以使用 collection.count() 对集合中的文档进行计数。您可以指定一个可选查询来确定要计数的文档。如果未指定查询,则该操作将对集合中的所有文档进行计数。
以下代码段计算描述一组商店中待售植物的文档集合中的文档数量:
const numPlants = await plants.count(); console.log(`There are ${numPlants} plants in the collection`);
运行此代码段将生成类似于以下内容的输出:
"There are 5 plants in the collection"
Update Documents
这些代码片段演示了如何从移动应用程序更新存储在 MongoDB 集合中的数据。更新操作使用查询来指定要更新的文档,并使用更新操作符来描述如何改变与查询匹配的文档。更新操作返回一个 Promise,该 Promise 解析为包含操作执行结果的对象。
更新单份文档
您可以使用collection.updateOne() 更新单个文档。
以下代码段更新文档集合中的单个文档,这些文档描述了一组商店中待售的植物。 此操作查询name
字段包含值“petunia”的文档,并将第一个匹配文档的sunlight
字段的值更改为“partial”:
const result = await plants.updateOne( { name: "petunia" }, { $set: { sunlight: "partial" } } ); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ matchedCount: 1, modifiedCount: 1 }
更新多个文档
您可以使用collection.updateMany() 更新单个文档。
以下代码段更新文档集合中的多个文档,这些文档描述一组商店中待售的植物。 此操作查询_partition
字段包含值“Store 42 ”的文档,并将每个匹配文档的_partition
字段的值更改为“Store 51 ”:
const result = await plants.updateMany( { _partition: "Store 42" }, { $set: { _partition: "Store 51" } } ); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ matchedCount: 4, modifiedCount: 4 }
更新或插入文档
如果更新操作与集合中的任何文档都不匹配,则您可以通过将 upsert
选项设置为 true
,自动将单个新文档插入到与更新查询匹配的集合。
以下代码片段更新某文档集合中的一份文档(这些文档描述一组商店中待售的植物),如果没有匹配该查询的文档,则插入一份新文档。此操作查询满足以下条件的文档:
sunlight
字段的值为“full”type
字段的值为“perennial”color
字段的值为“绿色”_partition
字段的值为“Store 47”
由于该代码片段将 upsert
选项设置为 true
,如果没有与查询匹配的文档,MongoDB 就会创建新文档,其中包括查询和指定的更新:
const result = await plants.updateOne( { sunlight: "full", type: "perennial", color: "green", _partition: "Store 47", }, { $set: { name: "super sweet basil" } }, { upsert: true } ); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ matchedCount: 0, modifiedCount: 0, upsertedId: ObjectId("5f1f63055512f2cb67f460a3"), }
Delete Documents
这些代码片段演示了如何从移动应用程序删除存储在 MongoDB 集合中的文档。删除操作使用查询来指定要删除的文档,并返回一个 Promise,该 Promise 解析为包含操作执行结果的对象。
删除单个文档
您可以使用collection.deleteOne() 从集合中删除单个文档。
以下代码段删除描述一组商店中待售植物的文档集合中的一个文档。 此操作查询color
字段值为“green”的文档,并删除与查询匹配的第一个文档:
const result = await plants.deleteOne({ color: "green" }); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ deletedCount: 1 }
删除多个文档
您可以使用collection.deleteMany() 从集合中删除多个项目。
以下代码片段删除“Store 42 ”中植物的所有文档,而这些文档位于描述一组商店中待售植物的文档集合中:
const result = await plants.deleteMany({ _partition: "Store 42", }); console.log(result);
运行此代码段将生成类似于以下内容的输出:
{ deletedCount: 4 }
注意更改
每当添加、修改或删除集合中的文档时,您都可以调用集合.watch()来订阅实时通知事件。 每个通知都会指定已更改的文档、更改方式以及导致事件的操作后的完整文档。
重要
无服务器限制
如果数据源为 Atlas 无服务器实例,则无法监控是否出现更改。MongoDB 无服务器当前不支持变更流,而变更流可用于受监视的集合以侦听是否存在更改。
Atlas App Services 使用监视集合上的 MongoDB 变更流来监听变更,并向订阅的客户端应用程序广播通知。如果您想知道用户在线时发生了什么事情,则这非常有用。例如:
跟踪交付位置
获取游戏的最新得分和统计数据
当用户发送新消息时,更新聊天线程
观察集合中的所有更改
要监视集合中的所有更改,请调用 collection.watch() 且不带任何参数:
for await (const change of plants.watch()) { let breakAsyncIterator = false; // Later used to exit async iterator switch (change.operationType) { case "insert": { const { documentKey, fullDocument } = change; console.log(`new document: ${documentKey}`, fullDocument); breakAsyncIterator = true; break; } case "update": { const { documentKey, fullDocument } = change; console.log(`updated document: ${documentKey}`, fullDocument); breakAsyncIterator = true; break; } case "replace": { const { documentKey, fullDocument } = change; console.log(`replaced document: ${documentKey}`, fullDocument); breakAsyncIterator = true; break; } case "delete": { const { documentKey } = change; console.log(`deleted document: ${documentKey}`); breakAsyncIterator = true; break; } } if (breakAsyncIterator) break; // Exit async iterator }
监视具有筛选器的集合中的更改
要监视集合中的特定变化,可使用指定要监视的变更事件值的查询,调用 collection.watch():
for await (const change of plants.watch({ filter: { operationType: "update", "fullDocument.type": "perennial", }, })) { // The change event will always represent a newly inserted perennial const { documentKey, fullDocument } = change; console.log(`new document: ${documentKey}`, fullDocument); break; // Exit async iterator }
聚合文档
聚合操作会通过一系列名为聚合管道的阶段来运行集合中的所有文档。聚合允许您过滤和转换文档、收集有关相关文档群组的摘要数据以及其他复杂的数据操作。
聚合操作接受聚合阶段列表作为输入,并返回 Promise,解析为管道处理过的文档集合。
聚合集合中的文档
您可以使用collection.aggregate() 执行聚合管道。
以下代码片段将按 type
值对 plants
集合中的所有文档进行分组,并汇总每种类型的数量:
const result = await plants.aggregate([ { $group: { _id: "$type", total: { $sum: 1 }, }, }, { $sort: { _id: 1 } }, ]); console.log(result);
运行此代码段将生成类似于以下内容的输出:
[ { _id: "annual", total: 3 }, { _id: "perennial", total: 2 }, ]
聚合阶段
筛选文档
您可以使用$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阶段聚合一个或多个文档的摘要数据。 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 阶段包含或省略文档中的特定字段,或使用聚合运算符计算新字段。投影有两种工作方式:
显式包含值为 1 的字段。此举的不利影响是会隐式排除所有未指定的字段。
隐式排除值为 0 的字段。此举的不利影响是会隐式包含所有未指定的字段。
这两种投影方法是互斥的:如果显式包含字段,则不能显式排除字段,反之亦然。
注意
_id
字段是一个特例:除非另行显式指定,否则它始终包含在每个查询中。因此,您可以排除具有 0
值的 _id
字段,同时包含其他字段,例如具有 1
的 _partition
。只有排除 _id
字段的特殊情况才允许在一个 $project
阶段中同时进行排除和包含。
{ "$project": { "<Field Name>": <0 | 1 | Expression>, ... } }
例子
以下 $project
阶段会省略 _id
字段,包含 name
字段,并创建一个名为 storeNumber
的新字段。storeNumber
是使用两个聚合运算符生成的:
$split
会将_partition
值分成位于空格字符两端的两个字符串段。例如,以此方式分割值“Store 42”会返回一个包含两个元素的数组:“Store”和“42”。$arrayElemAt
会根据第二个参数从数组中选择特定元素。在本例中,1
值会从$split
运算符生成的数组中选择第二个元素,因为数组索引会从0
开始。例如,传递给此操作的值 ["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
字段值的两个聚合运算符的输出。
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 会为该数组字段的每个元素复制每个文档一次,但同时还会在每个副本中用数组元素替换数组值。
{ $unwind: { path: <Array Field Path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> } }
例子
以下示例将为每个对象的 type
和 color
组合使用 $unwind
阶段。该聚合管道包含以下步骤:
将
$group
阶段与$addToSet
结合使用,从而为每个type
创建新文档,同时附带新字段colors
,而该字段包含集合中出现的该花卉类型的所有颜色的数组。使用
$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" }, ]