查询

¥Queries

Mongoose models增删改查操作 提供了几个静态辅助函数。这些函数中的每一个都返回一个 Mongoose Query 对象

¥Mongoose models provide several static helper functions for CRUD operations. Each of these functions returns a mongoose Query object.

Mongoose 查询可以通过两种方式之一执行。首先,如果传入 callback 函数,Mongoose 将异步执行查询并将结果传递给 callback

¥A mongoose query can be executed in one of two ways. First, if you pass in a callback function, Mongoose will execute the query asynchronously and pass the results to the callback.

查询也有 .then() 函数,因此可以用作 promise。

¥A query also has a .then() function, and thus can be used as a promise.

执行

¥Executing

执行查询时,你将查询指定为 JSON 文档。JSON 文档的语法与 MongoDB 外壳.1 相同。

¥When executing a query, you specify your query as a JSON document. The JSON document's syntax is the same as the MongoDB shell.

const Person = mongoose.model('Person', yourSchema);

// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields
const person = await Person.findOne({ 'name.last': 'Ghost' }, 'name occupation');
// Prints "Space Ghost is a talk show host".
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation);

person 是什么取决于操作:对于 findOne(),它是 可能为空的单个文档find()文档清单count()文档数量update()受影响的文档数量 等。模型的 API 文档 提供更多详细信息。

¥What person is depends on the operation: For findOne() it is a potentially-null single document, find() a list of documents, count() the number of documents, update() the number of documents affected, etc. The API docs for Models provide more details.

现在让我们看看不使用 await 时会发生什么:

¥Now let's look at what happens when no await is used:

// find each person with a last name matching 'Ghost'
const query = Person.findOne({ 'name.last': 'Ghost' });

// selecting the `name` and `occupation` fields
query.select('name occupation');

// execute the query at a later time
const person = await query.exec();
// Prints "Space Ghost is a talk show host."
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation);

在上面的代码中,query 变量的类型是 查询Query 使你能够使用链接语法构建查询,而不是指定 JSON 对象。以下 2 个示例是等效的。

¥In the above code, the query variable is of type Query. A Query enables you to build up a query using chaining syntax, rather than specifying a JSON object. The below 2 examples are equivalent.

// With a JSON doc
await Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  }).
  limit(10).
  sort({ occupation: -1 }).
  select({ name: 1, occupation: 1 }).
  exec();

// Using query builder
await Person.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec();

查询辅助函数可以在 API 文档中找到 的完整列表。

¥A full list of Query helper functions can be found in the API docs.

Queries are Not Promises

Mongoose 查询不是 promise。查询是 thenables,这意味着为了方便起见,它们有 .then() 方法用于 async/await。但是,与 Promise 不同的是,调用查询的 .then() 会执行查询,因此多次调用 then() 会抛出错误。

¥Mongoose queries are not promises. Queries are thenables, meaning they have a .then() method for async/await as a convenience. However, unlike promises, calling a query's .then() executes the query, so calling then() multiple times will throw an error.

const q = MyModel.updateMany({}, { isDeleted: true });

await q.then(() => console.log('Update 2'));
// Throws "Query was already executed: Test.updateMany({}, { isDeleted: true })"
await q.then(() => console.log('Update 3'));

对其他文件的引用

MongoDB 中没有联接,但有时我们仍然希望引用其他集合中的文档。这就是 population 发挥作用的地方。详细了解如何在查询结果中包含其他集合中的文档 此处

¥There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in. Read more about how to include documents from other collections in your query results here.

流式

你可以从 MongoDB 中查询结果。你需要调用 Query#cursor() 函数来返回 QueryCursor 的实例。

¥You can stream query results from MongoDB. You need to call the Query#cursor() function to return an instance of QueryCursor.

const cursor = Person.find({ occupation: /host/ }).cursor();

for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
  console.log(doc); // Prints documents one at a time
}

使用 异步迭代器 迭代 Mongoose 查询也会创建游标。

¥Iterating through a Mongoose query using async iterators also creates a cursor.

for await (const doc of Person.find()) {
  console.log(doc); // Prints documents one at a time
}

游标受 光标超时 约束。默认情况下,MongoDB 将在 10 分钟后关闭你的游标,后续的 next() 调用将导致 MongoServerError: cursor id 123 not found 错误。要覆盖此设置,请在光标上设置 noCursorTimeout 选项。

¥Cursors are subject to cursor timeouts. By default, MongoDB will close your cursor after 10 minutes and subsequent next() calls will result in a MongoServerError: cursor id 123 not found error. To override this, set the noCursorTimeout option on your cursor.

// MongoDB won't automatically close this cursor after 10 minutes.
const cursor = Person.find().cursor().addCursorFlag('noCursorTimeout', true);

但是,由于 会话空闲超时,游标仍然可能超时。因此,即使设置了 noCursorTimeout 的游标在 30 分钟不活动后仍然会超时。你可以在 MongoDB 文档 中阅读有关解决会话空闲超时的更多信息。

¥However, cursors can still time out because of session idle timeouts. So even a cursor with noCursorTimeout set will still time out after 30 minutes of inactivity. You can read more about working around session idle timeouts in the MongoDB documentation.

对比聚合

聚合 可以做许多与查询相同的事情。例如,以下是如何使用 aggregate() 查找 name.last = 'Ghost' 中的文档:

¥Aggregation can do many of the same things that queries can. For example, below is how you can use aggregate() to find docs where name.last = 'Ghost':

const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]);

然而,仅仅因为你可以使用 aggregate() 并不意味着你应该使用。一般来说,你应该尽可能使用查询,并且仅在绝对需要时才使用 aggregate()

¥However, just because you can use aggregate() doesn't mean you should. In general, you should use queries where possible, and only use aggregate() when you absolutely need to.

与查询结果不同,Mongoose 不会对结果进行 hydrate() 聚合。聚合结果始终是 POJO,而不是 Mongoose 文档。

¥Unlike query results, Mongoose does not hydrate() aggregation results. Aggregation results are always POJOs, not Mongoose documents.

const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]);

docs[0] instanceof mongoose.Document; // false

此外,与查询过滤器不同,Mongoose 也没有 cast 聚合管道。这意味着你有责任确保传入聚合管道的值具有正确的类型。

¥Also, unlike query filters, Mongoose also doesn't cast aggregation pipelines. That means you're responsible for ensuring the values you pass in to an aggregation pipeline have the correct type.

const doc = await Person.findOne();

const idString = doc._id.toString();

// Finds the `Person`, because Mongoose casts `idString` to an ObjectId
const queryRes = await Person.findOne({ _id: idString });

// Does **not** find the `Person`, because Mongoose doesn't cast aggregation
// pipelines.
const aggRes = await Person.aggregate([{ $match: { _id: idString } }]);

排序

排序 是确保查询结果按所需顺序返回的方法。

¥Sorting is how you can ensure your query results come back in the desired order.

const personSchema = new mongoose.Schema({
  age: Number
});

const Person = mongoose.model('Person', personSchema);
for (let i = 0; i < 10; i++) {
  await Person.create({ age: i });
}

await Person.find().sort({ age: -1 }); // returns age starting from 10 as the first entry
await Person.find().sort({ age: 1 }); // returns age starting from 0 as the first entry

当对多个字段进行排序时,排序键的顺序决定了 MongoDB 服务器首先按哪个键进行排序。

¥When sorting with mutiple fields, the order of the sort keys determines what key MongoDB server sorts by first.

const personSchema = new mongoose.Schema({
  age: Number,
  name: String,
  weight: Number
});

const Person = mongoose.model('Person', personSchema);
const iterations = 5;
for (let i = 0; i < iterations; i++) {
  await Person.create({
    age: Math.abs(2 - i),
    name: 'Test' + i,
    weight: Math.floor(Math.random() * 100) + 1
  });
}

await Person.find().sort({ age: 1, weight: -1 }); // returns age starting from 0, but while keeping that order will then sort by weight.

你可以在下面查看该块单次运行的输出。如你所见,年龄从 0 到 2 排序,但当年龄相等时,按体重排序。

¥You can view the output of a single run of this block below. As you can see, age is sorted from 0 to 2 but when age is equal, sorts by weight.

[
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb37'),
    age: 0,
    name: 'Test2',
    weight: 67,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb35'),
    age: 1,
    name: 'Test1',
    weight: 99,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb39'),
    age: 1,
    name: 'Test3',
    weight: 73,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb33'),
    age: 2,
    name: 'Test0',
    weight: 65,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb3b'),
    age: 2,
    name: 'Test4',
    weight: 62,
    __v: 0
  }
];

下一步

现在我们已经介绍了 Queries,让我们来看看 验证

¥Now that we've covered Queries, let's take a look at Validation.