如何在 MongoDB 中对集合记录中的数组进行排序?

Posted

技术标签:

【中文标题】如何在 MongoDB 中对集合记录中的数组进行排序?【英文标题】:How to sort array inside collection record in 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

但它不起作用。

【问题讨论】:

【参考方案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 中删除? curdbObjectList 之间的联系是什么?【参考方案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 中实现的方式(保持简单以便更容易理解) -

方法:

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

下面是工作 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 中的解决方案。

以上是关于如何在 MongoDB 中对集合记录中的数组进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MongoDB 根据数组元素对记录进行分组

批量查找 mongoDB 记录(使用 mongoid ruby​​ 适配器)

如何在 C# 中对数组执行集合减法?

如何在 PHP/Eclipse 中对 foreach 循环中从数组中拉出的自定义对象进行智能感知?

在 Mongo 集合中更新数组中的一条记录

MongoDB:如何在 100 个集合中找到 10 个随机文档?