Reputation: 2219
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();
}
}
Upvotes: 0
Views: 10397
Reputation: 8839
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();
}
Upvotes: 6
Reputation: 1
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))));
}
Upvotes: 0
Reputation: 41
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));
}
}
}
}
Upvotes: 1
Reputation: 1218
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)));
}
Upvotes: 0
Reputation: 1
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")))));
}
}
}
Upvotes: 0
Reputation: 1
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);
}
Upvotes: 0
Reputation: 733
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.
Upvotes: 0
Reputation: 11
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);
}
}
}
}
}
}
}
Upvotes: 1
Reputation: 1
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;
}
Upvotes: 0
Reputation: 21
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));
}
Upvotes: 2
Reputation: 71
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}}})})
Upvotes: 0
Reputation: 2219
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();
}
}
}
Upvotes: 2
Reputation: 76426
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
Upvotes: 0