0

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


}
6
  • Have you tried anything to achieve this? If yes, please share Commented Jan 21, 2015 at 15:40
  • @PradeepSimha Probably not because it's a well known homework question borrowed from university.mongodb.com Commented Jan 21, 2015 at 15:41
  • 1
    Have you tried to search for the lowest score and $pull it? Commented Jan 21, 2015 at 15:41
  • Sorry, I don't see what your question is. Please don't just ask "How do I do it?" Commented Jan 21, 2015 at 15:47
  • 1
    I just can't find the java example of removing an element of an array based on certain conditions of the array elements Commented Jan 21, 2015 at 16:12

13 Answers 13

6

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();
}
Sign up to request clarification or add additional context in comments.

Comments

2

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


}

}

Comments

2

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

Comments

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


            }


        }

    }
}

1 Comment

It would be a good idea to add why is this an answer to the problem instead of just posting the code.
1

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


}

Comments

0

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

1 Comment

Thanks, but i am looking for a java example of updating a doc by pulling a an element out of its embedded array based on certain conditions of array elements.
0

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

Comments

0

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

1 Comment

Welcome to Stack Overflow! While you may have solved this user's problem, code-only answers are not very helpful to users who come to this question in the future. Please edit your answer to explain why your code solves the original problem.
0

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.

Comments

0

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

        }

Comments

0

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

        }
    }
}

Comments

0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.