I'm attempting to use find (not aggregation) to give me the max score by student on a test. Essentially would be a sort by student, find top score for that student, then sort the result set. Here is the data:
{ "_id" : 1, "name" : "Pat", "score" : 97 }
{ "_id" : 1, "name" : "Pat", "score" : 92 }
{ "_id" : 2, "name" : "Pat", "score" : 89 }
{ "_id" : 3, "name" : "Ken", "score" : 91 }
{ "_id" : 4, "name" : "Ken", "score" : 81 }
I'm looking for the result to look like this (where only the students top score is returned):
{ "_id" : 1, "name" : "Pat", "score" : 97 }
{ "_id" : 2, "name" : "Ken", "score" : 91 }
I've tried many different combinations but can't get it to work. I know in SQL how I'd do it. Here is my current code, which is just sorting it:
db.grades.find().sort({score: -1})
You can use:
db.grades.find().sort('-score').distinct('name').sort('score')
Related
I'm learning MongoDB on my own. I have a collection with entries that look like this:
{
"_id" : ObjectId("5d0c13fbfdca455311248d6f"),
"borough" : "Brooklyn",
"grades" :
[
{ "date" : ISODate("2014-04-16T00:00:00Z"), "grade" : "A", "score" : 5 },
{ "date" : ISODate("2013-04-23T00:00:00Z"), "grade" : "B", "score" : 2 },
{ "date" : ISODate("2012-04-24T00:00:00Z"), "grade" : "A", "score" : 5 }
],
"name" : "C & C Catering Service",
"restaurant_id" : "40357437"
}
And I want to find all restaurants in Brooklyn with at least one grades.grade of A.
I've figured out the first half of the puzzle:
db.restaurants.find({borough:{$eq:"Brooklyn"}})
But how do I query in the "grades" array for grade A?
Use dot (.) to access and query nested objects:
db.restaurants.find({'borough':{$eq:"Brooklyn"}, 'grades.grade': 'A'})
db.restaurants.find({"borough" : "Brooklyn","grades.grade":"A"})
The query goes as follows
db.restaurants.find({$and : [{"grades.score" : {$gt:80}},{"grades.score" : {$lt:100}}]})
it was written with the idea in mind of retrieving restaurants with a score of 80 to 100
why does this query return the following document in its result. the below document does not have any grade element in the grades sub-document which falls in the range of "greater than 80 AND less than 100".
i have tried using this
{
"_id" : ObjectId("572eb5df1d739cc73c21fab1"),
"address" : {
"building" : "65",
"coord" : [
-73.9782725,
40.7624022
],
"street" : "West 54 Street",
"zipcode" : "10019"
},
"borough" : "Manhattan",
"cuisine" : "American ",
"grades" : [
{
"date" : ISODate("2014-08-22T00:00:00Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2014-03-28T00:00:00Z"),
"grade" : "C",
"score" : 131
},
{
"date" : ISODate("2013-09-25T00:00:00Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2013-04-08T00:00:00Z"),
"grade" : "B",
"score" : 25
},
{
"date" : ISODate("2012-10-15T00:00:00Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2011-10-19T00:00:00Z"),
"grade" : "A",
"score" : 13
}
],
"name" : "Murals On 54/Randolphs'S",
"restaurant_id" : "40372466"
}
This happens because both conditions are true. When you don't specify indices for grades.score it search through all elements of the array. And it does find an item with score > 80 (which is 131). At the same time there exist other items that are less then 100.
In human language you request "a document that contains score greater then 80 and at the same time contains score less then 100". You don't specify that it should be the same score that satisfy these conditions.
What you're looking for I guess is a query for simultaneously checking range for each element.
{ "grades" : { $elemMatch: { score: { $gt:80, $lt:100 } } } }
Yeah as said by #Kirill Slatin, your both conditions are true in that case. So, for your required result, check out the following solution with an explanation for your better understanding:
Using $lte and $gte separately,
db.products.find({score:{$gt:'80'}})
db.products.find({score:{$lt:'100'}})
First statement will return the result with score of 80 and gretaer than 80 and Second statement will return the score results with 100 and less than 100
Using $lt and $gt together
db.products.find({score:{$gt:80,$lt:100}})
The above Query will return the score results greater than 80 and less than 100
I'm trying to unite two collections using MapReduce. They have identical structure, for example:
db.tableR.insert({product:"A", quantity:150});
db.tableR.insert({product:"B", quantity:100});
db.tableR.insert({product:"C", quantity:60});
db.tableR.insert({product:"D", quantity:200});
db.tableS.insert({product:"A", quantity:150});
db.tableS.insert({product:"B", quantity:100});
db.tableS.insert({product:"F", quantity:220});
db.tableS.insert({product:"G", quantity:130});
I want MapReduce delete duplicates.
I'm creating a map that divides collection according quantity:
map = function(){
if (this.quantity<150){
var key=0;
}else{
var key=1;
}
var value = {"product":this.product, "quantity":this.quantity};
emit(key,value);
};
Now I want that reduce function removes duplicates but I can't find a way to add the new ones to the reduced var.
This is what I tried:
reduce = function(keys,values){
var reduced = {
product:"",
quantity:""
};
for (var i=0; i < values.length;i++)
{
if(values[i].product !== null) {reduced.insert({product: values[i].product, quantity: values[i].quantity})}
}
return reduced;};
db.tableR.mapReduce(map,reduce,{out:'map_reduce_result'});
db.tableS.mapReduce(map,reduce,{out:'map_reduce_result'});
db.map_reduce_result.find();
What function can I use?
My expected output:
{"_id" : 0, "value" : {"product" : "B","quantity" : 100}}
{"_id" : 0, "value" : {"product" : "C","quantity" : 60}}
{"_id" : 0, "value" : {"product" : "G","quantity" : 130}}
{"_id" : 1, "value" : {"product" : "A","quantity" : 150}}
{"_id" : 1, "value" : {"product" : "D","quantity" : 200}}
{"_id" : 1, "value" : {"product" : "F","quantity" : 220}}
The reduce function can only return a single value, so you want it to execute for every single row. The reduce function gets called for each unique key returned in your map function. Your keys were 0 and 1, so it would only get called twice for each collection - once for key 0 and once for key 1. Hence, the max number of results would only be 2 for each collection.
What you need to do is set the key to the product in the map function:
map = function(){
emit(this.product,{product:this.product,quantity:this.quantity});
};
Now, the reduce function will get called for every unique product value. Our new map function just returns the first value in the array (if there were duplicates in the same collection it would just take the first. You could be smart here and take the highest or lowest quantity - or the sum of the quantities, etc).
reduce = function(keys,values){
return values[0];
};
Run your first map reduce job:
db.tableR.mapReduce(map,reduce,{out:'map_reduce_result'});
Run your second, but this time merge the result:
db.tableS.mapReduce(map,reduce,{out: {merge: 'map_reduce_result'}});
Now db.map_reduce_result.find() returns:
{ "_id" : "A", "value" : { "product" : "A", "quantity" : 150 } }
{ "_id" : "B", "value" : { "product" : "B", "quantity" : 100 } }
{ "_id" : "C", "value" : { "product" : "C", "quantity" : 60 } }
{ "_id" : "D", "value" : { "product" : "D", "quantity" : 200 } }
{ "_id" : "F", "value" : { "product" : "F", "quantity" : 220 } }
{ "_id" : "G", "value" : { "product" : "G", "quantity" : 130 } }
Obviously the _id doesn't match what you are looking for. If you absolutely need that you can use the aggregation framework like so:
db.map_reduce_result.aggregate([{$project:{
_id:{$cond: { if: { $gte: [ "$value.quantity", 150 ] }, then: 1, else: 0 }},
value:1
}}]);
This results in:
{ "_id" : 1, "value" : { "product" : "A", "quantity" : 150 } }
{ "_id" : 0, "value" : { "product" : "B", "quantity" : 100 } }
{ "_id" : 0, "value" : { "product" : "C", "quantity" : 60 } }
{ "_id" : 1, "value" : { "product" : "D", "quantity" : 200 } }
{ "_id" : 1, "value" : { "product" : "F", "quantity" : 220 } }
{ "_id" : 0, "value" : { "product" : "G", "quantity" : 130 } }
Note: If two rows from different collections have the same product ID, but different quantities I am unsure which one will be returned.
I am total newbie to MongoDB. I was trying out basic stuff in mongo, when I encountered a problem. I searched for it, but not able to find any satisfactory answer.
I have a very simple collection named "users" having names and age of some persons. Below is output of db.users.find()
{ "_id" : ObjectId("566acc0442fea953b8d94a7e"), "name" : "gabriel", "age" : 22 }
{ "_id" : ObjectId("566acc0442fea953b8d94a7f"), "name" : "andy", "age" : 10 }
{ "_id" : ObjectId("566acc1342fea953b8d94a80"), "name" : "henry", "age" : 27 }
{ "_id" : ObjectId("566acc1342fea953b8d94a81"), "name" : "william", "age" : 19 }
{ "_id" : ObjectId("566acc3242fea953b8d94a82"), "name" : "sandra", "age" : 20 }
{ "_id" : ObjectId("566acc3242fea953b8d94a83"), "name" : "tom", "age" : 24 }
Now, I am trying to apply different projection on it. First two are:
db.users.find({}, {"name":1, "age":1})
{ "_id" : ObjectId("566acc0442fea953b8d94a7e"), "name" : "gabriel", "age" : 22 }
{ "_id" : ObjectId("566acc0442fea953b8d94a7f"), "name" : "andy", "age" : 10 }
{ "_id" : ObjectId("566acc1342fea953b8d94a80"), "name" : "henry", "age" : 27 }
{ "_id" : ObjectId("566acc1342fea953b8d94a81"), "name" : "william", "age" : 19 }
{ "_id" : ObjectId("566acc3242fea953b8d94a82"), "name" : "sandra", "age" : 20 }
{ "_id" : ObjectId("566acc3242fea953b8d94a83"), "name" : "tom", "age" : 24 }
db.users.find({}, {"name":0, "age":0})
{ "_id" : ObjectId("566acc0442fea953b8d94a7e") }
{ "_id" : ObjectId("566acc0442fea953b8d94a7f") }
{ "_id" : ObjectId("566acc1342fea953b8d94a80") }
{ "_id" : ObjectId("566acc1342fea953b8d94a81") }
{ "_id" : ObjectId("566acc3242fea953b8d94a82") }
{ "_id" : ObjectId("566acc3242fea953b8d94a83") }
are working just fine, but
db.users.find({}, {"name":0, "age":1})
Error: error: {
"$err" : "Can't canonicalize query: BadValue Projection cannot have a mix of inclusion and exclusion.",
"code" : 17287
}
is failing and giving error as shown above. I searched that problem may arise if there is conflicting conditions in projection, but I don't think there is something like that in my method call. Is there some rule like value of fields in find method should be either all ZERO or all ONE, but cannot be both. Please help.
If you want your query to return only the age, your projection must include only the fields you want to have. Not the one you don't want:
db.projection.find({}, {_id:0, age:1})
Exception for _id, you can specify to not include it.
Result
{
"age": 22
}
From the documentation:
A projection cannot contain both include and exclude specifications, except for the exclusion of the _id field. In projections that explicitly include fields, the _id field is the only field that you can explicitly exclude.
I have following records in mongodatabase.
> db.student.find()
{ "_id" : ObjectId("52ca76140e468ba197e50c23"), "name" : "pratik", "subject" : "maths", "marks" : 68 }
{ "_id" : ObjectId("52ca762b0e468ba197e50c24"), "name" : "pratik", "subject" : "biology", "marks" : 96 }
{ "_id" : ObjectId("52ca77a90e468ba197e50c25"), "name" : "pratik", "subject" : "maths", "marks" : 40 }
From this record I want to know the total marks obtained for just maths subject.
Here is what I have tried,but I don't know what is going wrong in the following query.
db.student.aggregate(
{$match: { 'subject': "maths"}},
{ $group : { _id :{ name:"$name",subject:"$subject",marks:"$marks" },
total: { $sum : "$marks"}}
})
{
"result" : [
{
"_id" : {
"name" : "pratik",
"subject" : "maths",
"marks" : 40
},
"total" : 40
},
{
"_id" : {
"name" : "pratik",
"subject" : "maths",
"marks" : 68
},
"total" : 68
}
],
"ok" : 1
}
Could you please let me know what has went wrong in the above query along with the correct answers.
Also suggest me the appropriate guide to use the aggregation module so that I can use it efficiently.I am beginner to aggregation module of mongo database.
Thanks in Advance.
The problem is the marks field in the group within the _id. It will group upon mark scores then which is useless to you, instead you want:
db.student.aggregate(
{$match: { 'subject': "maths"}},
{$group : {
_id :{ name:"$name", subject:"$subject" },
total: { $sum : "$marks"}
}}
);