Query to project fields in mongodb till sum = n - mongodb

Below is a sample collection result sorted by an attribute.
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "A",
"discount" : 10
},
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "B",
"discount" : 15
},
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "C",
"discount" : 20
},
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "D",
"discount" : 30
} .
Want to write a query which will project the docs where sum(discount) < n
Ex: find all docs till sum(discount) = 25 .
This should return first 2 docs .
Output:
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "A",
"discount" : 10
},
{
"_id" : ObjectId("5d96f8245e1ffa18e26dd2e2"),
"name" : "B",
"discount" : 15
}
find all docs till sum(discount) = 45 .
This should return first 3 docs .

Related

How to group by minimum value in nested arrays

I have a MongoDB collection with unique user traits and I'm trying to combine it with their orders, produce a new collection with the first order date and sum the total of orders by the user.
Given this example collection:
{
"_id" : 1,
"name" : "bob",
"orders" : [ { "date" : "2019-01-01", "amount" : 10 }, { "date" : "2019-01-02", "amount" : 10 } ]
}
{
"_id" : 1,
"name" : "lisa",
"orders" : [ { "date" : "2019-01-02", "amount" : 10 }, { "date" : "2019-01-03", "amount" : 15 } ]
}
this would be my desired output:
{
"_id" : 1,
"name" : "bob",
"first_order" : "2019-01-01",
"total_amount" : 20
}
{
"_id" : 2,
"name" : "lisa",
"first_order" : "2019-01-02",
"total_amount" : 25
}
Thank you

How to query mongodb with embedded document

Hi i'm practicing mongodb and I'm stuck with a problem. I'av the following set of documents.
{
"_id" : ObjectId("57cf9a134607674792dbad9e"),
"address" : {
"building" : "351",
"coord" : [
-73.9851356,
40.7676919
],
"street" : "West 57 Street",
"zipcode" : "10019"
},
"borough" : "Manhattan",
"cuisine" : "Irish",
"grades" : [
{
"date" : ISODate("2014-09-06T00:00:00.000Z"),
"grade" : "A",
"score" : 2
},
{
"date" : ISODate("2013-07-22T00:00:00.000Z"),
"grade" : "A",
"score" : 11
},
{
"date" : ISODate("2012-07-31T00:00:00.000Z"),
"grade" : "A",
"score" : 12
},
{
"date" : ISODate("2011-12-29T00:00:00.000Z"),
"grade" : "A",
"score" : 12
}
],
"name" : "Dj Reynolds Pub And Restaurant",
"restaurant_id" : "30191841"
}
I want to fetch list of all documents where zipcode is 10019
I'm following mongodb db tutorials and i've tried the following queries but nothing seems to work and i'm getting zero errors.
db.restaurants.find({address:{zipcode:10019}});
db.restaurants.find({"address.zipcode":10019})
zipcode is a string so your query should be
db.restaurants.find({ "address.zipcode": "10019" })
instead of
db.restaurants.find({ "address.zipcode": 10019 })

MongoDB SUM Of 2 Columns

I have a following documents in my collection:
{ "_id" : ObjectId("5785e5649b732ab238cfc519"), "name" : "Apple", "category" : "Fruit", "price" : 100, "discount" : 5 }
{ "_id" : ObjectId("5785e5709b732ab238cfc51a"), "name" : "Orange", "category" : "Fruit", "price" : 90, "discount" : 5 }
{ "_id" : ObjectId("5785e5819b732ab238cfc51b"), "name" : "PineApple", "category" : "Fruit", "price" : 60, "discount" : 2 }
{ "_id" : ObjectId("5785e5969b732ab238cfc51c"), "name" : "Potatto", "category" : "Vegetable", "price" : 10, "discount" : 1 }
{ "_id" : ObjectId("5785e5c39b732ab238cfc51d"), "name" : "Cabbage", "category" : "Vegetable", "price" : 5, "discount" : 1 }
And Expected Result
{ "_id" : { "category" : "Vegetable" }, "total" : 15 }
And I am using mongoDB query to find the Sum of total with vegetable category as follows
db.stall.aggregate([{$group: {_id: {category: "Vegetable" }, total: {$sum: "$price"}}}]);
But I am getting the following result
{ "_id" : { "category" : "Vegetable" }, "total" : 265 }
How should I find the sum of total and discount columns with vegetable category.
I'm not sure if I'm getting your question right but this will filter the sume of Price and sum of Discount for Vegetable category.
db.stall.aggregate([
{
{$match : {category : "Vegetable"}},
{$group : {_id: "$category", sumOfTotal : {$sum : "$price"}, sumOfDiscount : {$sum : "$discount"}}}
}
])

Union Set using MapReduce MongoDB

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.

Filling in with documents with default values after find/aggregate

I have a collection:
{ "name" : "A", "value" : 1, "date" : ISODate("2014-01-01T00:00:00.000Z") }
{ "name" : "B", "value" : 7, "date" : ISODate("2014-01-01T00:00:00.000Z") }
{ "name" : "A", "value" : 3, "date" : ISODate("2014-01-02T00:00:00.000Z") }
{ "name" : "B", "value" : 8, "date" : ISODate("2014-01-02T00:00:00.000Z") }
{ "name" : "B", "value" : 8, "date" : ISODate("2014-01-03T00:00:00.000Z") }
{ "name" : "A", "value" : 5, "date" : ISODate("2014-01-04T00:00:00.000Z") }
{ "name" : "A", "value" : 4, "date" : ISODate("2014-01-05T00:00:00.000Z") }
The document for A on 3rd Jan 2014 is not available. When I do a find/aggregate on A, I would like the document to appear in my result set with a default value (or better, value to be same as previous date). For example:
{ "name" : "A", "value" : 1, "date" : ISODate("2014-01-01T00:00:00.000Z") }
{ "name" : "A", "value" : 3, "date" : ISODate("2014-01-02T00:00:00.000Z") }
{ "name" : "A", "value" : 3 (or default value -1), "date" : ISODate("2014-01-03T00:00:00.000Z") }
{ "name" : "A", "value" : 5, "date" : ISODate("2014-01-04T00:00:00.000Z") }
{ "name" : "A", "value" : 4, "date" : ISODate("2014-01-05T00:00:00.000Z") }
How can this be done?
One thing you need in order to be able to do this in aggregation framework is an array of dates that you want your report to cover. For example, for input that you show, you might have an array:
days = [ ISODate("2014-01-01T00:00:00Z"), ISODate("2014-01-02T00:00:00Z"),
ISODate("2014-01-03T00:00:00Z"), ISODate("2014-01-04T00:00:00Z"),
ISODate("2014-01-05T00:00:00Z"), ISODate("2014-01-06T00:00:00Z") ];
to indicate that you want every one of these six days represented.
Here is the aggregation that you would run:
db.coll.aggregate( [
{$group : {_id:{name:"$name",date:"$date"},value:{$sum:"$value"}}},
{$group : {_id:"$_id.name", days:{$addToSet:"$_id.date"},docs:{$push:"$$ROOT"}}},
{$project : {missingDays:{$setDifference:[days,"$days"]},docs:1}},
{$unwind : "$missingDays"},
{$unwind : "$docs"},
{$group : {
_id:"$_id",
days:{$addToSet:{date:"$docs._id.date",value:"$docs.value"}},
missingDays:{$addToSet:{date:"$missingDays",value:{$literal:0}}}
} },
{$project : {_id:0, name:"$_id", date:{$setUnion:["$days","$missingDays"]}}},
{$unwind : "$date"},
{$sort : {date:1,name:1}}
] )
On your sample input with days defined as above it outputs:
{ "name" : "A", "date" : { "date" : ISODate("2014-01-01T00:00:00Z"), "value" : 1 } }
{ "name" : "A", "date" : { "date" : ISODate("2014-01-02T00:00:00Z"), "value" : 3 } }
{ "name" : "A", "date" : { "date" : ISODate("2014-01-03T00:00:00Z"), "value" : 0 } }
{ "name" : "A", "date" : { "date" : ISODate("2014-01-04T00:00:00Z"), "value" : 5 } }
{ "name" : "A", "date" : { "date" : ISODate("2014-01-05T00:00:00Z"), "value" : 4 } }
{ "name" : "A", "date" : { "date" : ISODate("2014-01-06T00:00:00Z"), "value" : 0 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-01T00:00:00Z"), "value" : 7 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-02T00:00:00Z"), "value" : 8 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-03T00:00:00Z"), "value" : 8 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-04T00:00:00Z"), "value" : 0 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-05T00:00:00Z"), "value" : 0 } }
{ "name" : "B", "date" : { "date" : ISODate("2014-01-06T00:00:00Z"), "value" : 0 } }
The first group stage may not be necessary in your case - it's there in case there are multiple documents for the same name and date, in that case you want to add the values for them. The second $group and $project stage figure out the difference between the days present for each name and the array of days you want covered, creating missingDays which will be getting the value 0 in the next $group stage. That group stage creates for each name an array of dates that have data and array of missing dates that don't. It structures them the say way so that the following $project stage can create a union of them using the $setUnion operator. After that all that's left is to $unwind the array of dates and sort it whichever way you want.