简体   繁体   中英

Mongodb Java: remove an element of an embedded array within a document based on certain condition of elements

I have a list of student documents which has the structure like this:

{
    "_id" : 0,
    "name" : "aimee Zank",
    "scores" : [
            {
                    "type" : "exam",
                    "score" : 1.463179736705023
            },
            {
                    "type" : "quiz",
                    "score" : 11.78273309957772
            },
            {
                    "type" : "homework",
                    "score" : 6.676176060654615
            },
            {
                    "type" : "homework",
                    "score" : 35.8740349954354
            }
    ]
}

As you can see, each student has a list of 4 scores. I need to remove the lowest "homework" score for each student document. Each student has 2 entries for "homewok" type scores (the last 2 entries in the array of 4 elements). The schema and ordering of score type is consistent and has the same pattern for all the students Your help is appreciated.

This is what I am have tried to achieve so far:

    DBCursor cursor = collection.find();

    try {

        while(cursor.hasNext()) {
           BasicDBObject doc = (BasicDBObject) cursor.next();
           BasicDBList scoreList =  (BasicDBList) doc.get("scores");

           BasicDBObject hw1 = (BasicDBObject) scoreList.get("2");
           double hw1Score = hw1.getDouble("score");

           BasicDBObject hw2 = (BasicDBObject) scoreList.get("3");
           double hw2Score = hw2.getDouble("score");

           if (hw1Score > hw2Score) {
               BasicDBObject update = new BasicDBObject("scores.score", hw2Score);               
               collection.update(doc, new BasicDBObject("$pull",update));                                                  
           } else {
               BasicDBObject update = new BasicDBObject("scores.score", hw1Score);

               collection.update(doc, new BasicDBObject("$pull",update));                                  
           }
           System.out.println(doc);
        }

    } finally {
       cursor.close();
    }


}

I know this is not the best solution (better approach is to sort the scores of homework for each document and then limit the array size to 3). But this works too :)

     try {

        while(cursor.hasNext()) {
           BasicDBObject doc = (BasicDBObject) cursor.next();
           BasicDBList scoreList =  (BasicDBList) doc.get("scores");              
           doc.remove("scores");

           BasicDBObject hw1 = (BasicDBObject) scoreList.get("2");
           double hw1Score = hw1.getDouble("score");

           BasicDBObject hw2 = (BasicDBObject) scoreList.get("3");
           double hw2Score = hw2.getDouble("score");

           if (hw1Score > hw2Score) {                                                  
               scoreList.remove(3);                     
           } else {
               scoreList.remove(2);                                          
           }
           doc.put("scores",scoreList);  
           collection.save(doc);
           System.out.println(doc);
        }

    } finally {
       cursor.close();
    }        


}

}

try this; I assume the highest score is 100 :

for (Document document : cursor) {
    ArrayList<Document> list =  (ArrayList<Document>) document.get("scores");
    double score = 100;
    for (Document doc : list) {
        if(doc.getString("type").equals("homework")){
            if(doc.getDouble("score") < score){
                score = doc.getDouble("score");
            }
        }
    }
    BasicDBObject update = new BasicDBObject("scores", new BasicDBObject("score", score).append("type", "homework"));
    collection.updateOne(document, new BasicDBObject("$pull", update));     
    }

all answers here are great. I just wanted to add that if someone wants to use the Java Operator (since driver v3.1), then rather than using the "$pull" operator, he can do something like:

...
Bson studentFilter = Filters.eq( "_id", doc.get("_id") );
Bson delete = Updates.pull("scores", new Document("score", lowestHomework).append("type", "homework"));
collection.updateOne(studentFilter, delete);

i think it is more elegant. So my complete answer would be:

public static void main(String[] args) {
    MongoClient client = new MongoClient();
    MongoDatabase database = client.getDatabase("school");
    MongoCollection<Document> collection = database.getCollection("students");

    List<Document> homeworks = collection.find()
        .into(new ArrayList<Document>());

    for(Document doc : homeworks)
    {
        ArrayList<Document> scores = (ArrayList<Document>) doc.get("scores");
        //iterate over the scores of each student (there are 4 scores: quiz, exam and 2*homework)
        double lowestHomework = Double.MAX_VALUE;
        for(Document embeddedDoc : scores)
        {
            if(embeddedDoc.getString("type").equals("homework"))
            {
                Double score = embeddedDoc.getDouble("score");
                if(score < lowestHomework)
                {
                    lowestHomework = score;
                }
            }
        }
        Bson studentFilter = Filters.eq( "_id", doc.get("_id") );
        Bson delete = Updates.pull("scores", new Document("score", lowestHomework).append("type", "homework"));

        collection.updateOne(studentFilter, delete);
    }

    client.close();
}
package com.mongodb;

import java.util.ArrayList;
import java.util.List;

import org.bson.Document;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

public class HWDeleteArray {

    public static void main(String[] args) {
        MongoClient client = new MongoClient();
        MongoDatabase database = client.getDatabase("school");
        MongoCollection<Document> collection = database.getCollection("students");

        List<Document> all = collection.find().into(new ArrayList<Document>());
        int i = 0;
        Double s1 =0.0;
        Double s2 =0.0;
        Document doc1 = null;
        Document doc2 = null;
        for(Document cur:all) {
            List<Document> scores = (List<Document>) cur.get("scores");
            for(Document score:scores) {
                if(score.getString("type").equals("homework")) {

                    if(i==0) {
                        i++;
                        s1 = (Double) score.get("score");
                        doc1 = score;

                    }else {
                        i--;
                        s2 = (Double) score.get("score");
                        doc2 = score;
                        if(s1 < s2) {
                            doc1.clear();
                            collection.replaceOne(new Document("_id",cur.get("_id")),cur);
                        }else {
                            doc2.clear();
                            collection.replaceOne(new Document("_id",cur.get("_id")),cur);
                        }
                    }
                }


            }


        }

    }
}

It's better to use the approach of using $pull with a filter in order to just remove the specific score from the array. The code below use the MongoDB Java Driver v3.6 with model API.

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
import static com.mongodb.client.model.Updates.pull;

public class RemoveLowestScoreArray {

    public static void main(String[] args) {
        MongoDatabase database;
        try (MongoClient client = new MongoClient()) {
            database = client.getDatabase("school");
            MongoCollection<Document> collection = database.getCollection("students");
            List<Document> students = collection.find().into(new ArrayList<>());

            for (Document student : students) {
                Document lowestScore = null;
                for (Document score : (List<Document>) student.get("scores")) {
                    if (score.getString("type").equals("homework")) {
                        if (lowestScore == null || score.getDouble("score") < (lowestScore.getDouble("score"))) {
                            lowestScore = score;
                        }
                    }
                }
                collection.updateOne(student, pull("scores", lowestScore));
            }
        }
    }


}

You iterate your array and find the minimum score. Pseudo-code:

min <- infinity
minIndex = -1
for index <- 0; index < elements.getScores().size(); index <- index + 1 do
    if min > elements.getScores().get(index) then
        min <- elements.getScores().get(index)
        minIndex <- index
    end if
end for

I tried using native mongodb commands which is preety simple to execute. I tried for the given problem statement an tested.Use the below 2 commands to make it work.

1) cursor = db.students.aggregate([{ "$unwind": "$scores" }, { "$match": { "scores.type": "homework"}},{ "$group": {'_id': '$_id', 'minitem': {'$min':"$scores.score"}}}]), null

2) cursor.forEach(function(coll) {db.students.update({'_id': coll._id}, {'$pull': {'scores': {'score': coll.minitem}}})})

I tried using Aggregater classes of MongoDB java driver to solve this. Please see below working code for reference.

AggregateIterable<Document> aiter = collection.aggregate(
                Arrays.asList(Aggregates.unwind("$scores"),Aggregates.match(Filters.eq("scores.type", "homework")),
                        Aggregates.sort(Sorts.orderBy(Sorts.ascending("_id"), Sorts.ascending("scores.score")))));

        collection = database.getCollection("students");
        MongoCursor<Document> cursor = aiter.iterator();
        int pid = -1;
        while (cursor.hasNext()) {
            Document doc = cursor.next();
            int cid = doc.getInteger("_id");
            double scoresScore = doc.get("scores", Document.class).getDouble("score");
            if (pid != cid) {
                // delete
                BasicDBObject update = new BasicDBObject("scores",
                        new BasicDBObject("score", scoresScore).append("type", "homework"));
                collection.updateOne(Filters.eq("_id", cid), new BasicDBObject("$pull", update));
            }
            pid = cid;
        }

This is my approach, maybe someone will find it cleaner and easier to understand:

MongoClient client = new MongoClient();
MongoDatabase database = client.getDatabase("school");
final MongoCollection<BasicDBObject> collection = database.getCollection("students",BasicDBObject.class);

MongoCursor<BasicDBObject> cursor = collection.find().iterator();

while(cursor.hasNext())
{
    double min_score = 999;
    BasicDBObject doc = cursor.next();
    BasicDBList scores = (BasicDBList) doc.get("scores");

    for (Object score : scores)
    {
        BasicDBObject x = (BasicDBObject) score;
        if (x.get("type").equals("homework"))
        {
            if (x.getDouble("score") < min_score)
            {
                min_score = x.getDouble("score");
            }
        }
    }

    if (min_score == 999){
        continue;
    }

    BasicDBObject query = new BasicDBObject("_id", doc.get("_id")); // Find this Document
    BasicDBObject fields = new BasicDBObject("scores",
            new BasicDBObject( "score", min_score)); // With those fields
    BasicDBObject update = new BasicDBObject("$pull",fields); // And remove any the matched results. 
    collection.updateOne(query, update);
}

The $pull operator removes from an existing array all instances of a value or values that match a specified condition.

Try this code:

import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.bson.conversions.Bson;
import static com.mongodb.client.model.Filters.eq;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Projections;


        MongoClient client = new MongoClient();


        String str, str2;
        Double sub;
        MongoDatabase db = client.getDatabase("school");
        MongoCollection<Document> coll = db.getCollection("students");
        //coll.drop();
        MongoCursor<Document> cursor = coll.find().iterator();

        List<Document> student = coll.find().into(new ArrayList<Document>());

        for(Document doc :student){
            List<Document> scores = (List<Document>)doc.get("scores");
            doc.remove("scores");


            List<Document> scores2 = scores.subList(2,3);
            System.out.println(scores2.toString());
            str = (scores2.toString()).substring(32, (scores2.toString()).length()-3);

            System.out.println(str);

            List<Document> scores3 = scores.subList(3,4);
            System.out.println(scores3.toString());
            str2 = (scores3.toString()).substring(32, (scores3.toString()).length()-3);
            System.out.println(str2);

            sub =  Double.parseDouble(str2) -  Double.parseDouble(str);


            if(sub >0){
                scores.remove(2);
                doc.put("scores", scores);


            }else if(sub == 0){
                scores.remove(2);
                doc.put("scores", scores);
            }else{
                scores.remove(3);
                doc.put("scores", scores);
            }
            Document cur = cursor.next();
            System.out.println(cur);
            System.out.println(doc);
            coll.findOneAndReplace(cur, doc);

        }

I dont know if its is the best option, but works:

List<Document> all = (List<Document>) collection.find().into(new ArrayList<Document>());

 for (Document current : all){
        Object id = current.get("_id");
        List<Document> i = (List<Document>) current.get("scores");

        if(i.get(2).getDouble("score")>i.get(3).getDouble("score")){
            collection.updateOne(new Document("_id", id),new Document("$pull",new Document("scores",new Document("score",i.get(3).getDouble("score")))));
        } else{
            collection.updateOne(new Document("_id", id),new Document("$pull",new Document("scores",new Document("score",i.get(2).getDouble("score")))));

        }
    }
}

This is my approach to resolve this problem.

    List<Document> documents = collection.find().into(new ArrayList<Document>());

    for (Document document : documents) {
        List<Document> scores = (List<Document>) document.get("scores");
        Document minDoc = null;
        for (Document score : scores) {
            if ("homework".equals(score.getString("type")) && (minDoc == null || minDoc.getDouble("score") > score.getDouble("score"))) {
                    minDoc = score;
                }
        }
        collection.updateOne(new Document("_id", document.get("_id")), new Document("$pull", new Document("scores", minDoc)));
    }

My solution for the above problem is :

    List<Document> results =
                        collection.aggregate(asList(
                                new Document("$unwind","$scores"),
                                new Document("$match", new Document("scores.type", new Document("$eq", "homework"))),
                                new Document("$group", new Document("_id", "$_id")
                                        .append("score", new Document("$min", "$scores.score")))))
                        .into(new ArrayList<Document>());
                for(Document doc : results)
                {
                    Integer id = doc.getInteger("_id");
                    Double score = doc.getDouble("score");
                    UpdateResult result = collection.updateOne(new Document("_id",new Document("$eq",id)), 
                            new Document("$pull", new Document("scores",
                                    new Document("score", score))));
                }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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