Mongodb embedded document - aggregation query - mongodb

I have got the below documents in Mongo database:
db.totaldemands.insert({ "data" : "UKToChina", "demandPerCountry" :
{ "from" : "UK" , to: "China" ,
"demandPerItem" : [ { "item" : "apples" , "demand" : 200 },
{ "item" : "plums" , "demand" : 100 }
] } });
db.totaldemands.insert({ "data" : "UKToSingapore",
"demandPerCountry" : { "from" : "UK" , to: "Singapore" ,
"demandPerItem" : [ { "item" : "apples" , "demand" : 100 },
{ "item" : "plums" , "demand" : 50 }
] } });
I need to write a query to find the count of apples exported from UK to any country.
I have tried the following query:
db.totaldemands.aggregate(
{ $match : { "demandPerCountry.from" : "UK" ,
"demandPerCountry.demandPerItem.item" : "apples" } },
{ $unwind : "$demandPerCountry.demandPerItem" },
{ $group : { "_id" : "$demandPerCountry.demandPerItem.item",
"total" : { $sum : "$demandPerCountry.demandPerItem.demand"
} } }
);
But it gives me the output with both apples and plums like below:
{ "_id" : "apples", "total" : 300 }
{ "_id" : "plums", "total" : 150 }
But, my expected output is:
{ "_id" : "apples", "total" : 300 }
So, How can I modify the above query to return only the count of apples exported from UK ?
Also, is there any other better way to achieve the output without unwinding ?

You can add another $match to get only apples.
As you have embedded document structure and performing aggregation, $unwind is required here. The alternate option could be map and reduce. However, unwind is most suitable here.
If you are thinking about performance, unwind shouldn't cause performance issue.
db.totaldemands.aggregate(
{ $match : { "demandPerCountry.from" : "UK" ,
"demandPerCountry.demandPerItem.item" : "apples" } },
{ $unwind : "$demandPerCountry.demandPerItem" },
{ $group : { "_id" : "$demandPerCountry.demandPerItem.item",
"total" : { $sum : "$demandPerCountry.demandPerItem.demand"
} } },
{$match : {"_id" : "apples"}}
);

Related

Mongo aggregation groups and subgroup

Hi I have a Mongo aggregation:
[
{
"$match" : {
"dateTime" : {
"$gte" : ISODate("2017-01-01T00:00:00.000+0000"),
"$lt" : ISODate("2018-01-01T00:00:00.000+0000")
}
}
},
{
"$group" : {
"_id" : "dateTime",
"totals" : {
"$sum" : "$payment.totalAmount"
},
"count" : {
"$sum" : 1.0
}
}
}
],
{
"allowDiskUse" : false
}
);
This works fine. It aggregates, and sums by date range I supplied and I get an output as follows.
{
"_id" : "dateTime",
"totals" : 2625293.825017198,
"count" : 12038.0
}
However, I also want to further refine the groupings.
I have a field called 'companyId' and I want to calculate the sum and count by each company Id for the given time range.
I would like to get an output similar to this, where I get a sum and count for each company ID in the date range I queried, not just a sum/count of all the data:
[
{
"companyId" : "Acme Co",
"totals" : 2625293.825017198,
"count" : 12038.0
},
{
"companyId" : "Beta Co",
"totals" : 162593.82198,
"count" : 138.0
},
{
"companyId" : "Cel Co",
"totals" : 593.82,
"count" : 38.0
}
]
How do I do this? I have not been able to find a good example online.
Thanks

Mongodb sort by sum of keys

I have a json document
{
{
"_id" : ObjectId("5715c4bbac530eb3018b456a"),
"content_id" : "5715c4bbac530eb3018b4569",
"views" : NumberLong(200),
"likes" : NumberLong(100),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c4bbac530eb3018b4568"),
"content_id" : "5715c4bbac530eb3018b4567",
"views" : NumberLong(300),
"likes" : NumberLong(200),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c502ac530ee5018b4956"),
"content_id" : "5715c502ac530ee5018b4955",
"views" : NumberLong(500),
"likes" : NumberLong(0),
"comments" : NumberLong(200)
}
}
How can we sort the document order by SUM("views", "likes", "comments")
something like in mysql
SELECT SUM(key1, key2, key3) AS key
FROM document
ORDER BY key
Thanks in advance.
First do a projection to obtain the sum of all the likes, views and comments, then sort based on that sum. I am considering group by content_id if is needed in the second snippet
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $sort : { "total" : 1 }}
])
If you need a group operation if content_id can be duplicated
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $group : { "_id" : "$_id" , totalPerId : { $sum : "$total" }}},
{ $sort : { "total" : 1 }}
])
Based on your test data, you will get:
{ "_id" : "5715c502ac530ee5018b4955", "totalPerId" : NumberLong(700) }
{ "_id" : "5715c4bbac530eb3018b4567", "totalPerId" : NumberLong(500) }
{ "_id" : "5715c4bbac530eb3018b4569", "totalPerId" : NumberLong(300) }

$avg in mongodb aggregation

Document looks like this:
{
"_id" : ObjectId("361de42f1938e89b179dda42"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{ "NAME" : "asd", "OBTAINED_SCORE" : 30}, { "NAME" : "acd", "OBTAINED_SCORE" : 36}
]
}
Aggregation function:
db.coll.aggregate([ {$unwind:"$SCORES"}, {$group : { _id : { user_id : "$user_id", evaluator_id : "$evaluator_id"}, AVG_SCORE : { $avg : "$SCORES.OBTAINED_SCORE" }}} ])
Suppose if there are two documents with same "user_id" (say u1) and different "evaluator_id" (say e1 and e2).
For example:
1) Average will work like this ((30 + 20) / 2 = 25). This is working for me.
2) But for { evaluator_id : "e1" } document, score is 30 for { "NAME" : "asd" } and { evaluator_id : "e2" } document, score is 0 for { "NAME" : "asd" }. In this case, I want the AVG_SCORE to be 30 only (not (30 + 0) / 2 = 15).
Is it possible through aggregation??
Could any one help me out.
It's possible by placing a $match between the $unwind and $group aggregation pipelines to first filter the arrays which match the specified condition to include in the average computation and that is, score array where the obtained score is not equal to 0 "SCORES.OBTAINED_SCORE" : { $ne : 0 }
db.coll.aggregate([
{
$unwind: "$SCORES"
},
{
$match : {
"SCORES.OBTAINED_SCORE" : { $ne : 0 }
}
},
{
$group : {
_id : {
user_id : "$user_id",
evaluator_id : "$evaluator_id"
},
AVG_SCORE : {
$avg : "$SCORES.OBTAINED_SCORE"
}
}
}
])
For example, the aggregation result for this document:
{
"_id" : ObjectId("5500aaeaa7ef65c7460fa3d9"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{
"NAME" : "asd",
"OBTAINED_SCORE" : 0
},
{
"NAME" : "acd",
"OBTAINED_SCORE" : 36
}
]
}
will yield:
{
"result" : [
{
"_id" : {
"user_id" : "u1",
"evaluator_id" : "e1"
},
"AVG_SCORE" : 36
}
],
"ok" : 1
}

mongodb find all records with subdocument id equals to

i have the following collection records:
> db.products.find(ObjectId("53a9a6aad901f2961403fc9b")).pretty()
{
"_id" : ObjectId("53a9a6aad901f2961403fc9b"),
"code" : "N39",
"name" : {
"en-UK" : "N39"
},
"weight" : [
90
],
"collectionId" : ObjectId("53a9a6a8d901f2961403fbe2"),
"fabric_composition" : [
{
"fabricId" : ObjectId("53a9a6a9d901f2961403fc69"),
"value" : 70
}
{
"fabricId" : ObjectId("53a9a6a9d901f2961403fc6a"),
"value" : 30
}
],
"visible" : "true",
"manufacturer" : "53a859d9d901f2e8f81ac83b"
}
and
> db.fabric.find().pretty()
{
"_id" : ObjectId("53a9a6a9d901f2961403fc69"),
"name" : [
{
"en-UK" : "Recycled Organic Cotton"
}
]
}
{
"_id" : ObjectId("53a9a6a9d901f2961403fc6a"),
"name" : [
{
"en-UK" : "Recycled Polyester"
}
]
}
how do i query the mongodb collection products to list all products that have a fabric_composition with ObjectId for Recycled Organic Cotton as an example?
any advice much appreciated
You need to use use dot notation to query subdocuments:
db.products.find({
"fabric_composition.fabricId" : ObjectId("53a9a6a9d901f2961403fc69")
});
This query will return all documents that have at least one sub-document with fabricId you're looking for.

How do I keep documents in aggregation with $unwind

Lets say I have three students...
Alice, she is Always there on fridays.
{
"name" : "Alice",
"goes" : {
"mondays" : {
"fr" : 900,
"to" : 1400
},
"fridays" : {
"fr" : 700,
"to" : 1600
},
}
}
And bob, here should be there on the first of january
{
"_id" : ObjectId("5284a7085d60338b40b8f17d"),
"name" : "Bob",
"goes" : {
"mondays" : {
"fr" : 800,
"to" : 1200
},
"special" : [
{
"date" : "2010-01-01",
"fr" : 1000,
"to" : 1500
}
]
}
}
And Clair who will not be attenging on mondays or at 10.00
{
"_id" : ObjectId("5284c2785d60338b40b8f17f"),
"name" : "Clair",
"goes" : {
"wednesdays" : {
"fr" : 1100,
"to" : 1500
},
"special" : [
{
"date" : "2010-01-01",
"fr" : 1600,
"to" : 1900
},
{
"date" : "2010-01-02",
"fr" : 1000,
"to" : 1300
}
]
}
}
I want to find all students that should attend on fridays at 7 och 10 on the first of January 2010
So I do this with the aggregation framework.
db.students.aggregate(
[
{
$unwind: "$goes.special"
},
{
$match: {
$or : [
{
'goes.fridays.fr': 700,
},
{
'goes.special.date' : '2010-01-01',
'goes.special.fr': 1000
}
]
}
}
]
)
But Alice does not show up. It clearly states why in the mongodb docs, http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/ at the very bottom.
"If you specify a target field for $unwind that holds an empty array
([]) in an input document, the pipeline ignores the input document,
and will generates no result documents."
I could solve it by adding an array with a null value in it but that does not seam like a nice solution.
Is there a way I could get unwind NOT to ignore documents that does not have data in a $unwind'ed array?
You don't need $unwind at all. Simple $match in pipeline is enough:
pipeline = [
{
"$match" : {
"$or" : [
{
"goes.fridays.fr" : 700
},
{
"goes.special" : {
"$elemMatch" : {
"date" : "2010-01-01",
"fr" : 1000
}
}
}
]
}
}
]
db.students.aggregate(pipeline)
It can be done easily even without aggregation framework.
query = {
"$or" : [
{
"goes.fridays.fr" : 700
},
{
"goes.special" : {
"$elemMatch" : {
"date" : "2010-01-01",
"fr" : 1000
}
}
}
]
}
db.students.find(query)