【问题标题】:How to sort documents based on length of an Array field如何根据数组字段的长度对文档进行排序
【发布时间】:2015-11-10 21:21:47
【问题描述】:

在我的小型 ExpressJS 应用程序中,我有一个这样定义的 Question 模型

var mongoose = require('mongoose'),
    Schema   = mongoose.Schema;

/**
 * Question Schema
 */
var Question = new Schema({
  title: {
    type: String,
    default: '',
    trim: true,
    required: 'Title cannot be blank'
  },
  content: {
    type: String,
    default: '',
    trim: true
  },
  created: {
    type: Date,
    default: Date.now
  },
  updated: {
    type: Date,
    default: Date.now
  },
  author: {
    type: Schema.ObjectId,
    ref: 'User',
    require: true
  },
  answers : [{
    type: Schema.ObjectId,
    ref: 'Answer'
  }]
});

module.exports = mongoose.model('Question', Question);

我想根据答案数字列出热门问题。我用来执行我的目的的查询

Question.find()
  .sort({'answers.length': -1})
  .limit(5)
  .exec(function(err, data) {
    if (err) return next(err);
    return res.status(200).send(data);
  });

但我什么也得不到。你有什么解决办法吗?

【问题讨论】:

    标签: node.js mongodb mongodb-query aggregation-framework


    【解决方案1】:

    3.4 版中的新功能。是.addFields,它确实简化了这个调用:

    const sortBy = "answers"
    const orderBy = 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending'
    const questions = await Questions.aggregate()
              .addFields({"length": {"$size": `$${sortBy}`}}) //adds a new field, to the existing ones (incl. _id)
              .sort({"length": orderBy })
    return res.json(questions)

    注意:我在module.exports{} async function 中使用调用。因此,awaitreturn 部分是必要的。但是addFieldsfunction(err, result) 的工作方式类似。

    【讨论】:

      【解决方案2】:

      您似乎在这里的意思是您希望根据“answers”数组的“长度”对结果进行“排序”,而不是您的语法所暗示的称为“长度”的“属性”。作为记录,这里的语法是不可能的,因为您的模型是“引用的”,这意味着该集合文档中数组字段中存在的唯一数据是那些引用文档的 ObjectId 值。

      但是您可以使用.aggregate() 方法和$size 运算符来做到这一点:

      Question.aggregate(
          [
              { "$project": {
                  "title": 1,
                  "content": 1,
                  "created": 1,
                  "updated": 1,
                  "author": 1,
                  "answers": 1,
                  "length": { "$size": "$answers" }
              }},
              { "$sort": { "length": -1 } },
              { "$limit": 5 }
          ],
          function(err,results) {
              // results in here
          }
      )
      

      聚合管道分阶段工作。首先,结果中的字段有一个$project,这里使用$size返回指定数组的长度。

      现在有一个带有“长度”的字段,您可以跟随带有 $sort$limit 的阶段,它们在聚合管道中被应用为它们自己的阶段。

      更好的方法是始终在文档中保留“答案”数组的长度属性。这使得无需其他操作即可轻松进行排序和查询。使用 $inc 操作符就像数组中的 $push$pull 项目一样,维护这一点很简单:

      Question.findByIdAndUpdate(id,
          {
              "$push": { "answers": answerId },
              "$inc": { "answerLength": 1 } 
          },
          function(err,doc) {
      
          }
      )
      

      删除时反之:

      Question.findByIdAndUpdate(id,
          {
              "$pull": { "answers": answerId },
              "$inc": { "answerLength": -1 } 
          },
          function(err,doc) {
      
          }
      )
      

      即使您不使用原子操作符,同样的原则也适用于您在进行过程中更新“长度”的地方。那么用排序查询就很简单了:

      Question.find().sort({ "answerLength": -1 }).limit(5).exec(function(err,result) {
      
      });
      

      因为该属性已经存在于文档中。

      因此,要么使用 .aggregate() 而不更改您的数据,要么更改您的数据以始终将长度作为属性包含在内,这样您的查询将非常快。

      【讨论】:

      • 这对我有用。感谢您提供有用的解决方案和澄清。
      【解决方案3】:

      你也可以使用:

      db.question.find().sort({"answers":-1}).limit(5).pretty();
      

      【讨论】:

      • 那么数组字段默认是按照数组长度排序的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-05
      • 1970-01-01
      • 2017-04-02
      • 1970-01-01
      • 2021-02-27
      • 1970-01-01
      相关资源
      最近更新 更多