【问题标题】:How to sort array inside collection record in MongoDB?如何在 MongoDB 中对集合记录中的数组进行排序?
【发布时间】:2012-11-07 03:26:05
【问题描述】:

我有一组学生,每个学生的记录如下所示,我想按score 的降序对scores 数组进行排序。

这个咒语在 mongo shell 上是什么样子的?

> db.students.find({'_id': 1}).pretty()
{
        "_id" : 1,
        "name" : "Aurelia Menendez",
        "scores" : [
                {
                        "type" : "exam",
                        "score" : 60.06045071030959
                },
                {
                        "type" : "quiz",
                        "score" : 52.79790691903873
                },
                {
                        "type" : "homework",
                        "score" : 71.76133439165544
                },
                {
                        "type" : "homework",
                        "score" : 34.85718117893772
                }
        ]
}

我正在尝试这个咒语......

 doc = db.students.find()

 for (_id,score) in doc.scores:
     print _id,score

但它不起作用。

【问题讨论】:

    标签: javascript arrays mongodb sorting


    【解决方案1】:

    您将需要在应用程序代码中操作嵌入数组或使用 MongoDB 2.2 中的新 Aggregation Framework。

    mongo shell 中的示例聚合:

    db.students.aggregate(
        // Initial document match (uses index, if a suitable one is available)
        { $match: {
            _id : 1
        }},
    
        // Expand the scores array into a stream of documents
        { $unwind: '$scores' },
    
        // Filter to 'homework' scores 
        { $match: {
            'scores.type': 'homework'
        }},
    
        // Sort in descending order
        { $sort: {
            'scores.score': -1
        }}
    )
    

    示例输出:

    {
        "result" : [
            {
                "_id" : 1,
                "name" : "Aurelia Menendez",
                "scores" : {
                    "type" : "homework",
                    "score" : 71.76133439165544
                }
            },
            {
                "_id" : 1,
                "name" : "Aurelia Menendez",
                "scores" : {
                    "type" : "homework",
                    "score" : 34.85718117893772
                }
            }
        ],
        "ok" : 1
    }
    

    【讨论】:

    • 您可以将聚合管道的末尾更改为按升序排序(因此最低值在前)并限制为 1 个文档:` { $sort: { 'scores.score': 1 }} , { $limit : 1 }`
    • 重复数据的问题,你要在每个对象中重复名称。所以如果我在上层有 20 个字段,那么我应该重复吗?
    • @PrabjotSingh 我不完全清楚您的问题是什么,但您应该发布一个新问题,其中包含文档结构、所需输出和 MongoDB 服务器版本的示例,而不是在 cmets 中讨论/驱动程序。
    • 我同意@PrabjotSingh 分数作为嵌入式数组返回?正如问题所暗示的那样。
    • @F.O.O 这个问题已有 6.5 年历史,现在有不同的选项,具体取决于您的 MongoDB 服务器版本。请发布一个新问题,其中包含与您的环境相关的详细信息以及您要解决的问题。
    【解决方案2】:

    由于这个问题可以通过不同的方式进行管理,我想说另一种解决方案是“插入和排序”,这样您将在创建 Find() 的那一刻得到 Ordered 数组。

    考虑这些数据:

    {
       "_id" : 5,
       "quizzes" : [
          { "wk": 1, "score" : 10 },
          { "wk": 2, "score" : 8 },
          { "wk": 3, "score" : 5 },
          { "wk": 4, "score" : 6 }
       ]
    }
    

    这里我们将更新文档,进行排序。

    db.students.update(
       { _id: 5 },
       {
         $push: {
           quizzes: {
              $each: [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ],
              $sort: { score: -1 },
              $slice: 3 // keep the first 3 values
           }
         }
       }
    )
    

    结果是:

    {
      "_id" : 5,
      "quizzes" : [
         { "wk" : 1, "score" : 10 },
         { "wk" : 2, "score" : 8 },
         { "wk" : 5, "score" : 8 }
      ]
    }
    

    文档: https://docs.mongodb.com/manual/reference/operator/update/sort/#up._S_sort

    【讨论】:

    • 我们可以在存储的数组字段上使用 $each 吗?
    【解决方案3】:

    这就是我们可以用 JS 和 mongo 控制台解决这个问题的方法:

    db.students.find({"scores.type": "homework"}).forEach(
      function(s){
        var sortedScores = s.scores.sort(
          function(a, b){
            return a.score<b.score && a.type=="homework";
          }
        );
        var lowestHomeworkScore = sortedScores[sortedScores.length-1].score;
        db.students.update({_id: s._id},{$pull: {scores: {score: lowestHomeworkScore}}}, {multi: true});
      })
    

    【讨论】:

    • 老兄?你破坏了乐趣。
    • find() 中的{"scores.type": "homework"} 过滤表达式有什么作用吗?
    • @TreefishZhang 为什么不应该呢?
    • @AlexanderPanasyuk 它实现了什么? -它过滤掉了一些学生吗?
    【解决方案4】:

    这里是java代码,可用于找出数组中的最低分数并将其删除。

    public class sortArrayInsideDocument{
    public static void main(String[] args) throws UnknownHostException {
        MongoClient client = new MongoClient();
        DB db = client.getDB("school");
        DBCollection lines = db.getCollection("students");
        DBCursor cursor = lines.find();
        try {
            while (cursor.hasNext()) {
                DBObject cur = cursor.next();
                BasicDBList dbObjectList = (BasicDBList) cur.get("scores");
                Double lowestScore = new Double(0);
                BasicDBObject dbObject = null;
                for (Object doc : dbObjectList) {
                    BasicDBObject basicDBObject = (BasicDBObject) doc;
                    if (basicDBObject.get("type").equals("homework")) {
                        Double latestScore = (Double) basicDBObject
                                .get("score");
                        if (lowestScore.compareTo(Double.valueOf(0)) == 0) {
                            lowestScore = latestScore;
                            dbObject = basicDBObject;
    
                        } else if (lowestScore.compareTo(latestScore) > 0) {
                            lowestScore = latestScore;
                            dbObject = basicDBObject;
                        }
                    }
                }
                // remove the lowest score here.
                System.out.println("object to be removed : " + dbObject + ":"
                        + dbObjectList.remove(dbObject));
                // update the collection
                lines.update(new BasicDBObject("_id", cur.get("_id")), cur,
                        true, false);
            }
        } finally {
            cursor.close();
        }
    }
    }
    

    【讨论】:

    • 不错!很好的例子...使用 java 8 我们可以最小化比较部分。
    • @Vel 如何从dbObjectList 中删除dbObject,从cur DBObject 中删除? cur 和 dbObjectList 之间的联系是什么?
    【解决方案5】:

    要对数组进行排序,请按照下列步骤操作:

    1) 使用 unwind 遍历数组

    2)排序数组

    3)使用group将数组的对象合并为一个数组

    4)然后投影其他字段

    查询

    db.taskDetails.aggregate([
        {$unwind:"$counter_offer"},
        {$match:{_id:ObjectId('5bfbc0f9ac2a73278459efc1')}},
        {$sort:{"counter_offer.Counter_offer_Amount":1}},
       {$unwind:"$counter_offer"},
       {"$group" : {_id:"$_id",
        counter_offer:{ $push: "$counter_offer" },
        "task_name": { "$first": "$task_name"},
        "task_status": { "$first": "$task_status"},
        "task_location": { "$first": "$task_location"},
    }}
    
    ]).pretty()
    

    【讨论】:

    • 与$addToSet相反,使用$push保留数组的顺序,在上一步排序。
    【解决方案6】:

    从 Mongo 5.2release schedule 开始,这是新的 $sortArray 聚合运算符的确切用例:

    // {
    //   name: "Aurelia Menendez",
    //   scores: [
    //     { type: "exam",     score: 60.06 }
    //     { type: "quiz",     score: 52.79 }
    //     { type: "homework", score: 71.76 }
    //     { type: "homework", score: 34.85 }
    //   ]
    // }
    db.collection.aggregate([
      { $set: {
        scores: {
          $sortArray: {
            input: "$scores",
            sortBy: { score: -1 }
          }
        }
      }}
    ])
    // {
    //   name: "Aurelia Menendez",
    //   scores: [
    //     { type: "homework", score: 71.76 },
    //     { type: "exam",     score: 60.06 },
    //     { type: "quiz",     score: 52.79 },
    //     { type: "homework", score: 34.85 }
    //   ]
    // }
    

    这个:

    • 排序 ($sortArray) scores 数组 (input: "$scores")
    • 通过对scores (sortBy: { score: -1 }) 应用排序
    • 无需组合使用昂贵的 $unwind、$sort 和 $group 阶段

    【讨论】:

      【解决方案7】:

      这很容易猜到,但无论如何,尽量不要在 mongo 大学课程上作弊,因为那样你就不会了解基础知识。

      db.students.find({}).forEach(function(student){ 
      
          var minHomeworkScore,  
              scoresObjects = student.scores,
              homeworkArray = scoresObjects.map(
                  function(obj){
                      return obj.score;
                  }
              ); 
      
          minHomeworkScore = Math.min.apply(Math, homeworkArray);
      
          scoresObjects.forEach(function(scoreObject){ 
              if(scoreObject.score === minHomeworkScore){ 
                  scoresObjects.splice(scoresObjects.indexOf(minHomeworkScore), 1); 
              } 
          });
      
          printjson(scoresObjects);
      
      });
      

      【讨论】:

        【解决方案8】:

        Order Title 和 Array title 并返回整个集合数据 Collection name is menu

        [
                    {
                        "_id": "5f27c5132160a22f005fd50d",
                        "title": "Gift By Category",
                        "children": [
                            {
                                "title": "Ethnic Gift Items",
                                "s": "/gift?by=Category&name=Ethnic"
                            },
                            {
                                "title": "Novelty Gift Items",
                                "link": "/gift?by=Category&name=Novelty"
                            }
                        ],
                        "active": true
                    },
                    {
                        "_id": "5f2752fc2160a22f005fd50b",
                        "title": "Gift By Occasion",
                        "children": [
                            {
                                "title": "Gifts for Diwali",
                                "link": "/gift-for-diwali" 
                            },
                            {
                                "title": "Gifts for Ganesh Chathurthi",
                                "link": "/gift-for-ganesh-chaturthi",
                            }
                        ],
                        
                        "active": true
                    }
            ]
        

        如下查询

        let menuList  = await  Menu.aggregate([
                        { 
                            $unwind: '$children'
                        }, 
                        {
                            $sort:{"children.title":1}
                        },
                        {   
                            $group : { _id : "$_id",
                                root: { $mergeObjects: '$$ROOT' },   
                                children: { $push: "$children" } 
                            } 
                        },
                        {
                            $replaceRoot: {
                                newRoot: {
                                    $mergeObjects: ['$root', '$$ROOT']
                                }
                            }
                        },
                        {
                            $project: {
                                root: 0 
                            }
                        },
                        { 
                            $match: {
                                        $and:[{'active':true}],
                                    }
                        },
                        {
                            $sort:{"title":1}
                        }                  
            ]);
        

        【讨论】:

          【解决方案9】:

          我相信你正在做M101P: MongoDB for Developers,其中作业 3.1 是从两个作业分数中删除较低的一个。由于到目前为止还没有教授聚合,因此您可以执行以下操作:

          import pymongo
          
          conn = pymongo.MongoClient('mongodb://localhost:27017')
          db = conn.school
          students = db.students
          
          for student_data in students.find():
              smaller_homework_score_seq = None
              smaller_homework_score_val = None
              for score_seq, score_data in enumerate(student_data['scores']):
                  if score_data['type'] == 'homework':
                      if smaller_homework_score_seq is None or smaller_homework_score_val > score_data['score']:
                          smaller_homework_score_seq = score_seq
                          smaller_homework_score_val = score_data['score']
              students.update({'_id': student_data['_id']}, {'$pop': {'scores': smaller_homework_score_seq}})
          

          【讨论】:

          • OP 用于 mongo js shell,但这是一个超级干净的 Python 示例!
          【解决方案10】:

          这是我使用 pyMongo(MongoDB 的 Python 驱动程序)的方法:

          import pymongo
          
          
          conn = pymongo.MongoClient('mongodb://localhost')
          
          def remove_lowest_hw():
              db = conn.school
              students = db.students
          
              # first sort scores in ascending order
              students.update_many({}, {'$push':{'scores':{'$each':[], '$sort':{'score': 1}}}})
          
              # then collect the lowest homework score for each student via projection
              cursor = students.find({}, {'scores':{'$elemMatch':{'type':'homework'}}})
          
              # iterate over each student, trimming each of the lowest homework score
              for stu in cursor:
                  students.update({'_id':stu['_id']}, {'$pull':{'scores':{'score':stu['scores'][0]['score']}}})
          
          remove_lowest_hw()
          
          conn.close()
          

          【讨论】:

            【解决方案11】:

            这项工作对我来说,这是一个有点粗略的代码,但每个学生的最低任务的结果都是正确的。

            var scores_homework = []
            db.students.find({"scores.type": "homework"}).forEach(
              function(s){
                s.scores.forEach(
                    function(ss){
                        if(ss.type=="homework"){
                            ss.student_id = s._id
                            scores_homework.push(ss)
                        }
                    }
                )
            })
            for(i = 0; i < scores_homework.length; i++)
            {
                var b = i+1;
                var ss1 = scores_homework[i];
                var ss2 = scores_homework[b];
                var lowest_score = {};
                if(ss1.score > ss2.score){
                    lowest_score.type = ss2.type;
                    lowest_score.score = ss2.score;
                    db.students.update({_id: ss2.student_id},{$pull: {scores: {score: lowest_score.score}}});
                }else if(ss1.score < ss2.score){
                    lowest_score.type = ss1.type;
                    lowest_score.score = ss1.score;
                    db.students.update({_id: ss1.student_id},{$pull: {scores: {score: lowest_score.score}}});
                }else{
                    lowest_score.type = ss1.type;
                    lowest_score.score = ss1.score;
                    db.students.update({_id: ss1.student_id},{$pull: {scores: {score: lowest_score.score}}});
                }
                i++
            }
            

            【讨论】:

              【解决方案12】:

              这就是我在 Java 中实现的方式(保持简单以便更容易理解) -

              方法:

              1. 从student集合中获取scores数组
              2. 从type == homework的scores数组中获取所有score值
              3. 对分数值进行排序,使最低的成为第一个元素 [score.get(0)]
              4. 然后,遍历主 scores 并创建 score 数组的新副本,同时跳过 type == homework && score == scores.get(0) 的元素
              5. 最后,将新的分数数组更新为学生文档。

              下面是工作 Java 代码:

                  public void removeLowestScore(){
                  //Create mongo client and database connection and get collection
                  MongoClient client = new MongoClient("localhost");
                  MongoDatabase database = client.getDatabase("school");
                  MongoCollection<Document> collection = database.getCollection("students");
              
              
                  FindIterable<Document> docs = collection.find();
                  for (Document document : docs) {
              
                      //Get scores array
                      ArrayList<Document> scores = document.get("scores", ArrayList.class);           
              
                      //Create a list of scores where type = homework
                      List<Double> homeworkScores = new ArrayList<Double>();
                      for (Document score : scores) {
                          if(score.getString("type").equalsIgnoreCase("homework")){
                              homeworkScores.add(score.getDouble("score"));   
                          }
                      }
              
                      //sort homework scores
                      Collections.sort(homeworkScores);
              
                      //Create a new list to update into student collection
                      List<Document> newScoresArray = new ArrayList<Document>();
                      Document scoreDoc = null;
              
                      //Below loop populates new score array with eliminating lowest score of "type" = "homework"
                      for (Document score : scores) {
                          if(score.getString("type").equalsIgnoreCase("homework") && homeworkScores.get(0) == score.getDouble("score")){                  
                                  continue;                       
                              }else{
                                  scoreDoc = new Document("type",score.getString("type"));
                                  scoreDoc.append("score",score.getDouble("score"));
                                  newScoresArray.add(scoreDoc);
                              }               
                          }           
              
                      //Update the scores array for every student using student _id
                      collection.updateOne(Filters.eq("_id", document.getInteger("_id")), new Document("$set",new Document("scores",newScoresArray)));
                  }       
              }
              

              【讨论】:

                【解决方案13】:

                当然已经很晚了,但我只想在 Mongo Shell 上贡献我自己的解决方案:

                var students = db.getCollection('students').find({});
                for(i = 0 ; i < students.length(); i++) {
                    var scores = students[i].scores;
                    var tmp = [];
                    var min = -1 ;
                    var valueTmp = {};
                    for(j = 0 ; j < scores.length; j++) {        
                        if(scores[j].type != 'homework') {
                            tmp.push(scores[j]);
                        } else {
                            if (min == -1) {
                                min = scores[j].score;
                                valueTmp = scores[j];
                            } else {
                                if (min > scores[j].score) {
                                    min = scores[j].score;
                                    tmp.push(valueTmp);
                                    valueTmp = scores[j];
                                } else {
                                    tmp.push(scores[j]);
                                }
                            }
                        }
                    }
                    db.students.updateOne({_id:students[i]._id},
                                            {$set:{scores:tmp}});
                }
                

                【讨论】:

                  【解决方案14】:

                  @Stennie 的回答很好,也许$group 运算符对保留原始文档很有用,而不会在许多文档中爆炸(按分数)。

                  我只是在为您的应用程序使用 javascript 时添加另一种解决方案。

                  如果只查询一个文档,有时用 JS 对嵌入数组进行排序比进行聚合更容易。 当你的文档有很多字段时,比使用$push操作符更好,否则你必须将所有字段一一推送,或者使用$$ROOT操作符(我错了吗?)

                  我的示例代码使用 Mongoose.js : 假设您已经初始化了 Student 模型。

                  // Sorting
                  function compare(a, b) {
                    return a.score - b.score;
                  }
                  
                  Students.findById('1', function(err, foundDocument){
                    foundDocument.scores = foundDocument.scores.sort(compare);
                    
                    // do what you want here...
                    // foundModel keeps all its fields
                  });
                  

                  【讨论】:

                    【解决方案15】:

                    按分数排序可以很简单:

                    db.students.find({_id:137}).sort({score:-1}).pretty()
                    

                    但你需要找到 type:homework ...

                    【讨论】:

                    • $sort 与 $push 和 $each 一起使用将在 mongo shell 中执行:db.students.updateMany({}, {'$push':{'scores':{'$each':[], '$sort':{'score': 1}}}}) per mongodb documentation
                    【解决方案16】:

                    应该是这样的:

                    db.students.find().sort(scores: ({"score":-1}));
                    

                    【讨论】:

                    • 如果这是用 mongoshell 编写的,这是无效的,它也不会完成他要求的工作。正确它应该是 db.students.find().sort({"scores.score":-1}) 但这不会对任何东西进行排序(至少我什么都看不到),尤其是学生中的分数数组。据我所知,您需要手动遍历这些数组条目并进行排序,mongo 不会这样做。
                    • 也 philnate 是正确的......这不会在 mongo shell 中带来预期的结果......感谢您的尝试。
                    • 我是 python 和 mongodb 菜鸟...此链接导致 PHP 中的答案...我正在寻找 python 或 mongo shell 中的解决方案。
                    猜你喜欢
                    • 2017-11-01
                    • 1970-01-01
                    • 2016-07-18
                    • 2016-07-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-11-30
                    • 1970-01-01
                    相关资源
                    最近更新 更多