13

假设我有一些架构,它有一个像这样的虚拟字段

var schema = new mongoose.Schema(
{
    name: { type: String }
},
{
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
});

schema.virtual("name_length").get(function(){
    return this.name.length;
});

在查询中是否可以按虚拟字段对结果进行排序?就像是

schema.find().sort("name_length").limit(5).exec(function(docs){ ... });

当我尝试这个时,结果很简单,没有排序......

4

2 回答 2

13

您将无法按虚拟字段排序,因为它们未存储到数据库中。

虚拟属性是便于使用但不会持久化到 mongodb 的属性。

http://mongoosejs.com/docs/2.7.x/docs/virtuals.html

于 2012-11-19T11:51:06.313 回答
2

Schema 中定义的 Virtuals 不会注入到生成的 MongoDB 查询中。一旦从数据库中检索到这些函数,它们就会在适当的时刻为每个文档运行。

为了达到您想要实现的目标,您还需要在 MongoDB 查询中定义虚拟字段。例如,在聚合的 $project 阶段。

但是,在按虚拟字段排序时需要牢记以下几点:

  • 投影文档只在内存中可用,所以如果我们只添加一个字段并将搜索结果的整个文档在排序之前存储在内存中,将会带来巨大的性能成本
  • 由于以上原因,排序时根本不会使用索引

这是一个关于如何在保持相对良好性能的同时按虚拟字段排序的一般示例:

想象一下,您有一组球队,每个球队都包含直接存储在文档中的球员数组。现在,要求要求我们按照 favouredPlayer 的排名对这些球队进行排序,其中 favouredPlayer 基本上是一个虚拟属性,包含在特定条件下球队最相关的球员(在这个例子中,我们只想考虑进攻和防守球员) . 此外,上述标准取决于用户的选择,因此不能保留在文档中。

最重要的是,我们的“团队”文档非常大,所以为了减轻内存排序对性能的影响,我们只投影我们需要排序的字段,然后在限制结果后恢复原始文档。

查询:

[
  // find all teams from germany
  { '$match': { country: 'de' } },
  // project only the sort-relevant fields
  // and add the virtual favoredPlayer field to each team
  { '$project': {
    rank: 1,
    'favoredPlayer': {
      '$arrayElemAt': [
        {
          // keep only players that match our criteria
          $filter: {
            input: '$players',
            as: 'p',
            cond: { $in: ['$$p.position', ['offense', 'defense']] },
          },
        },
        // take first of the filtered players since players are already sorted by relevance in our db
        0,
      ],
    },
  }},
  // sort teams by the ranking of the favoredPlayer
  { '$sort': { 'favoredPlayer.ranking': -1, rank: -1 } },
  { '$limit': 10 },
  // $lookup, $unwind, and $replaceRoot are in order to restore the original database document
  { '$lookup': { from: 'teams', localField: '_id', foreignField: '_id', as: 'subdoc' } },
  { '$unwind': { path: '$subdoc' } },
  { '$replaceRoot': { newRoot: '$subdoc' } },
];

对于您上面给出的示例,代码可能如下所示:

var schema = new mongoose.Schema(
  { name: { type: String } },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true },
  });

schema.virtual('name_length').get(function () {
  return this.name.length;
});

const MyModel = mongoose.model('Thing', schema);

MyModel
  .aggregate()
  .project({
    'name_length': {
      '$strLenCP': '$name',
    },
  })
  .sort({ 'name_length': -1 })
  .exec(function(err, docs) {
    console.log(docs);
  });
于 2021-11-16T14:41:49.310 回答