简体   繁体   English

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

[英]How to sort array inside collection record in MongoDB?

I have a collection of students, each with a record that looks like the following and I want to sort the scores array in descending order of score .我有个同学的集合,每个看起来像下面,我想那种创纪录的scores降序的顺序排列score

what does that incantation look like on the mongo shell?这个咒语在 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
                }
        ]
}

I'm trying this incantation....我正在尝试这个咒语......

 doc = db.students.find()

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

but it's not working.但它不起作用。

You will need to manipulate the embedded array in your application code or using the new Aggregation Framework in MongoDB 2.2.您将需要在应用程序代码中操作嵌入的数组或使用 MongoDB 2.2 中的新聚合框架

Example aggregation in the mongo shell: 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
    }}
)

Sample output:示例输出:

{
    "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
}

Since this question can be managed in different ways i want to say that another solution is "insert and sort", in this way you will get the Ordered array at the moment you will made a Find().由于可以通过不同方式管理这个问题,我想说另一种解决方案是“插入和排序”,这样您将在进行 Find() 时获得 Ordered 数组。

Consider this data:考虑这个数据:

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

Here we will update the Document, make the Sort.在这里,我们将更新文档,进行排序。

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

Result is:结果是:

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

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

That's how we could solve this with JS and mongo console:这就是我们如何用 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});
  })

Here is the java code which can be used to find out the lowest score in the array and remove it.这是 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();
    }
}
}

In order to sort array, follow these steps:为了对数组进行排序,请按照下列步骤操作:

1) use unwind to iterate through array 1)使用 unwind 遍历数组

2) sort array 2)排序数组

3) use group to merge objects of array into one array 3)使用 group 将数组的对象合并为一个数组

4) then project other fields 4)然后投影其他领域

Query询问

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()

It's easy enough to guess, but anyway, try not cheat with mongo university courses because you won't understand basics then.这很容易猜到,但无论如何,请不要在 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);

});

I believe you are doing M101P: MongoDB for Developers where homework 3.1 is to remove the lower one from two homework scores.我相信你在做M101P: MongoDB for Developers其中作业 3.1 是从两个作业分数中删除较低的一个。 Since aggregations were not taught up to that point you can do something like this:由于到目前为止还没有教授聚合,因此您可以执行以下操作:

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

Order Title and Array title also and return whole collection data 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
            }
    ]

Query as below查询如下

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

the answer of @Stennie is fine, maybe a $group operator would be useful to keep the original document, without exploding it in many documents (one by score). @Stennie 的回答很好,也许$group操作符对于保留原始文档很有用,而不会在许多文档中将其分解(一个分数)。

I just add another solution when using javascript for your application .我只是在为您的应用程序使用 javascript 时添加了另一个解决方案

if you query only one document, it's sometimes easier to sort the embedded array by JS, instead of doing an aggregate.如果只查询一个文档,有时通过 JS 对嵌入的数组进行排序会更容易,而不是进行聚合。 When your document has a lot of fields, it's even better than using $push operator, otherwise you've to push all the fields one by one, or use $$ROOT operator (am I wrong ?)当你的文档有很多字段时,它甚至比使用$push运算符更好,否则你必须一个一个地推送所有字段,或者使用$$ROOT运算符(我错了吗?)

My example code uses Mongoose.js : Suppose you have initialized you Students model.我的示例代码使用Mongoose.js :假设您已初始化您的 Students 模型。

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

this work for me, it is a little rough code but the results of the lowest tasks for each student are correct.这项工作对我来说是一个有点粗糙的代码,但每个学生的最低任务的结果是正确的。

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++
}

This is my approach using pyMongo, the Python driver to MongoDB:这是我使用 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()

This is how I have implemented in Java (Have kept it simple so that it's easier to understand) -这就是我在 Java 中实现的方式(保持简单以便更容易理解)-

Approach :方法 :

  1. Get scores array from student collection学生集合中获取分数数组
  2. Get all score values from scores array where type == homework类型 == 作业的分数数组中获取所有分数
  3. Sort the score values so that lowest becomes 1st element [score.get(0)]对分值进行排序,使最低成为第一个元素 [score.get(0)]
  4. Then, loop through the main scores and create new copy of scores array while skipping elements where type == homework && score == scores.get(0)然后,遍历主要分数并创建分数数组的新副本,同时跳过类型 == 作业 && 分数 == 分数.get(0) 的元素
  5. Finally, update the new scores array to student document.最后,将新的分数数组更新到学生文档。

Below is working Java code:下面是工作 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)));
    }       
}

Certainly it's late, but I just want to contribute my own solution on Mongo Shell:当然已经晚了,但我只想在 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}});
}

Starting in Mongo 5.2 release schedule , it's the exact use case for the new $sortArray aggregation operator:Mongo 5.2 release 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 }
//   ]
// }

This:这:

  • sorts ( $sortArray ) the scores array ( input: "$scores" )排序( $sortArrayscores数组( input: "$scores"
  • by applying a sort on score s ( sortBy: { score: -1 } )通过对score s 应用排序( sortBy: { score: -1 }
  • without having to apply a combination of expensive $unwind , $sort and $group stages无需应用昂贵的$unwind$sort$group阶段的组合

sort by the score can be simple like:按分数排序可以很简单,例如:

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

but you need to find the one for type:homework ...但你需要找到一个 type:homework ...

它应该是这样的:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM