Docs 菜单
Docs 主页
/ /
Atlas Device SDKs
/ /

查询 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;
};

要从客户端应用程序访问关联集群,请将集群名称传递给 User.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 flytrap”植物的文档:

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()会匹配集合中的所有文档。

以下代码片段在描述一组商店中待售植物的文档集合中查找所有描述多年生植物的文档:

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()。

以下代码片段会将描述“铃兰”植物的单个文档插入到描述一组商店中待售植物的文档集合中:

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()。

以下代码段将三个描述植物的文档插入到描述一组商店中待售植物的文档集合中:

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()。

以下代码段更新文档集合中的单个文档,这些文档描述了一组商店中待售的植物。 此操作查询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 47 ”的文档,并将每个匹配文档的_partition字段的值更改为“Store 51 ”:

const result = await plants.updateMany(
{ _partition: "Store 47" },
{ $set: { _partition: "Store 51" } }
);
console.log(result);
{ matchedCount: 3, modifiedCount: 3 }

要插入文档,请在更新操作中将 upsert 选项设为 true。如果此操作的查询与集合中的所有文档均不匹配,upsert 操作则会自动将单个新文档插入到集合中,而该新文档与所提供的查询文档及其应用的更新相匹配。

以下代码片段将通过一个 upsert 操作来更新文档集合中的某一文档,而这些文档描述了一组商店中出售的植物。此查询与所有现有文档均不匹配,因此 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()。如果未传递查询,或是此查询匹配多个文档,此操作则会删除其找到的第一个文档。

以下代码段删除描述一组商店中待售植物的文档集合中的一个文档。 此操作查询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.aggregation()。聚合操作返回管道中最后一个阶段的结果集。

以下代码片段将按 type 值对 plants 集合中的所有文档进行分组,并汇总每种类型的数量:

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阶段聚合一个或多个文档的摘要数据。 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 是使用两个聚合运算符生成的:

  1. $split 会将 _partition 值分成位于空格字符两端的两个字符串段。例如,以此方式分割值“Store 42”会返回一个包含两个元素的数组:“Store”和“42”。

  2. $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 阶段将包含数组的单个文档转换为包含该数组中各个值的多个文档。当您展开数组字段时,MongoDB 会为该数组字段的每个元素复制每个文档一次,但同时还会在每个副本中用数组元素替换数组值。

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

例子

以下示例将为每个对象的 typecolor 组合使用 $unwind 阶段。该聚合管道包含以下步骤:

  1. $group 阶段与 $addToSet 结合使用,从而为每个 type 创建新文档,同时附带新字段 colors,而该字段包含集合中出现的该花卉类型的所有颜色的数组。

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

后退

调用函数