如何根据数组字段的长度对文档进行排序

How to sort documents based on length of an Array field

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

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);
  });

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

你在这里的意思似乎是你想 "sort" 你的结果基于 "answers" 数组的 "length",而不是 "property" 称为 "length" 正如你的语法所暗示的那样。作为记录,该语法在这里是不可能的,因为您的模型是 "referenced",这意味着该集合文档中数组字段中存在的唯一数据是那些引用文档的 ObjectId 值。

但是您可以使用 .aggregate() method and the $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到return指定数组的长度。

现在有一个带有 "length" 的字段,您可以跟随带有 $sort and $limit 的阶段,这些阶段在聚合管道中作为自己的阶段应用。

更好的方法是始终保持文档中 "answers" 数组的长度 属性。这使得无需其他操作即可轻松进行排序和查询。使用数组中的 $inc operator as you $push or $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) {

    }
)

即使您不使用原子运算符,在您进行过程中更新 "length" 时,同样的原则也适用。然后排序查询就简单了:

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

});

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

因此,要么在不更改数据的情况下使用 .aggregate() 进行操作,要么更改数据以始终将长度包含为 属性,这样您的查询就会非常快。

您也可以使用:

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

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) 的工作方式类似。

"answers.length" 将不起作用,除非长度是 属性 个答案 写数组 属性 name

就够了
Question.find({}).sort({ "answers": -1 }).limit(5)

+1 从小到大 -1 从大到小

您还可以添加多个字段来处理长度关系并始终保证相同的结果,也可以对日期进行排序

.sort({ "answers": -1, "created": 1 })