Order by Array of documents in aggregation mongodb - mongodb

I have a collection with documents like these
{
"Day" : 1
"Name" : "Fred",
"Badges" : ["STAR", "FAST"]
}
{
"Day" : 2
"Name" : "Fred",
"Badges" : ["STAR", "NEWBIE", "SLOW"]
}
{
"Day" : 3
"Name" : "Fred",
"Badges" : ["STAR", "NEWBIE", "FAST"]
}
{
"Day" : 1
"Name" : "Frank",
"Badges" : ["STAR","GO"]
}
{
"Day" : 2
"Name" : "Frank",
"Badges" : ["STAR"]
}
I aggregated these documents for all month in order to return a statistic like this
{
Name : "Fred"
Badges : [{Name : "STAR", Count : 3},
{Name : "NEWBIE", Count : 2},
{Name : "FAST", Count : 2},
{Name : "SLOW", Count : 1}
]
}
and the same thing for Frank.
{
Name : "Fred"
Badges : [{Name : "STAR", Count : 2},
{Name : "GO", Count : 1}
]
}
I have a problem with the sort .
How can I order for badge value STAR ?
Could I change the aggregation query in order to produce different result ?
I think that a structure like this can be good solution but I don't understand how to obtain it
{Name : "Fred"
STAR : 2
GO : 1}
Thanks

db.a.aggregate([
{$unwind: "$Badges"},
{$group: {_id: {name: "$Name", badge: "$Badges"}, numberOfBadges: { $sum: 1}}},
{$project: {_id: 0,name: "$_id.name", badge: "$_id.badge", numberOfBadges:1, orderField: {$cond: [{$eq: ["$_id.badge", "STAR"]}, "$numberOfBadges", 0]}}},
{$sort: {orderField:-1}}])
As far as I know you cannot have dynamic field names in aggregation framework so you can't have a field name as STAR. But you can make a trick by adding a new field to your document such as orderField and set the number of numberOfBadges to that document if the badge is STAR, otherwise set 0 to that field. Then sort on orderField.

Related

MongoDB Join Fields Aggregation

In MongoDB, I have a collection of different movies with their years.
Consider these documents:
{
"_id" : ObjectId("63a994974ac549c5ea982d2b"),
"title" : "Destroyer",
"year" : 1907
},
{
"_id" : ObjectId("63a994974ac549c5ea982d2a"),
"title" : "Aquaman",
"year" : 1902
},
{
"_id" : ObjectId("63a994974ac549c5ea982d29"),
"title" : "On the Basis of Sex",
"year" : 1907
},
{
"_id" : ObjectId("63a994974ac549c5ea982d28"),
"title" : "Holmes and Watson",
"year" : 1902
},
{
"_id" : ObjectId("63a994974ac549c5ea982d27"),
"title" : "Conundrum: Secrets Among Friends",
"year" : 1902
},
{
"_id" : ObjectId("63a994974ac549c5ea982d26"),
"title" : "Welcome to Marwen",
"year" : 1907
},
{
"_id" : ObjectId("63a994974ac549c5ea982d25"),
"title" : "Mary Poppins Returns",
"year" : 1997
},
{
"_id" : ObjectId("63a994974ac549c5ea982d24"),
"title" : "Bumblebee",
"year" : 2004
}
I want to show the year or years with the fewest movies showing the number of movies from that year. So, with the previous documents, you can see there are 2 years with the same count of movies. Years: 1907 and 1902.
Therefore, I want to join those years in a single document. I tried this code:
var query1 = {$group: {"_id": "$year",
"movies": {$sum:1},
"Years": {$addToSet:"$year"},
}}
var stages = [query1]
db.movies.aggregate(stages)
However, the output is this:
{
"_id" : 1907,
"movies" : 3,
"Years" : [ 1907 ]
},
{
"_id" : 1902,
"movies" : 3,
"Years" : [ 1902 ]
},
I do not want that. The expect output that I want is this:
{
"_id" : 1902,
"movies" : 3,
"Years" : [ 1907, 1902 ]
}
Once you get that, what I want to show as a final output is this:
{
"_id" : [1907, 1902],
"movies" : 3
}
I do not know how to do that. I cannot join all these years in an array...
How can I get that? How can I obtain the previous output?
Thanks so much for your attention. Whatever you need, ask it pls...
One option is to do what #Joe suggested:
db.collection.aggregate([
{$group: {
_id: "$year",
count: {$sum: 1}
}},
{$group: {
_id: "$count",
years: {$push: "$_id"}
}},
{$sort: {_id: -1}},
{$limit: 1},
{$project: {_id: 0, count: "$_id", years: 1}}
])
See how it works on the playground example
If two values are Equal make a Condition and change datatype after trying to concatenates save in Same array or use trim to four letter ',' and store Database then Fetch the movies value and this new Concatenate array.
I am not sure This I just give my IdEA
The _id for the group is $year, so every year will have a separate document.
What you should do is 2 group stages, the first with _id: "$year" to count the number of movies per year, and the second, with the _id: $movies with the addToSet to group years with the same number of movies

How do I match a field of a collection with a field inside array of another collection

I have 2 collection in MongoDb and say :
Collection1
[ {"Name" : "foo1", "Place" : "bar1", "Rank" : 1},
{"Name" : "foo2", "Place" : "bar2", "Rank" : 2},
{"Name" : "foo3", "Place" : "bar3", "Rank" : 3} ]
Collection2
[ {"DocNo" : "One",
"DocArray" : [{"Name" : "ABC", "Number" : 1},
{"Name" : "DEF", "Number" : 2},
{"Name" : "XYZ", "Number" : 4}]},
{"DocNo" : "Two",
"DocArray" : [{"Name" : "JKL", "Number" : 5},
{"Name" : "GHI", "Number" : 1},
{"Name" : "UVW", "Number" : 3}]}
]
I need to write a query in Collection1 where I have the DocNO to get the search from Collection2 and return only those documents from Collection1 where Rank field matches all the Number fields of all elements inside the DocArray for that particular document.
In this example if I have DocNo = "Two" then the returned array would be something like this :
[ {"Name" : "foo1", "Place" : "bar1", "Rank" : 1},
{"Name" : "foo3", "Place" : "bar3", "Rank" : 3}]
I need to do all this in a single mongo query
Your requirement :
I need to write a query in Collection1
But I would say you need to start this aggregation on Collection 2 & use $lookup stage on Collection 1
Query :
db.Collection2.aggregate([
{ $match: { DocNo: "Two" } }, // Filter for matching doc
{
$lookup: {
from: "Collection1",
localField: "DocArray.Number",
foreignField: "Rank",
as: "Collection1_docs"
}
}
])
Test : mongoplayground

Get matched embedded document(s) from array

I've got a lot of documents using the following structure in MongoDB:
{
"_id" : ObjectId("..."),
"plant" : "XY_4711",
"hour" : 1473321600,
"units" : [
{
"_id" : ObjectId("..."),
"unit_id" : 10951,
"values" : [
{
"quarter" : 1473321600,
"value" : 395,
},
{
"quarter" : 1473322500,
"value" : 402,
},
{
"quarter" : 1473323400,
"value" : 406,
},
{
"quarter" : 1473324300,
"value" : 410,
}
]
}
]
}
Now I need to find all embedded document values where the quarter is between some given timestamps (eg: { $gte: 1473324300, $lte: 1473328800 }).
I've only got the unit_id and the quarter timestamp from/to for filtering the documents. And I only need the quarter and value grouped and ordered by unit.
I'm new in MongoDB and read something about find() and aggregate(). But I don't know how to do it. MongoDB 3.0 is installed on the server.
Finally I've got it:
I simply have to take apart each array, filtering out the things I don't need and put it back together:
db.collection.aggregate([
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$unwind: "$units"},
{$unwind: "$units.values"},
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$project: {"units": {values: {quarter: 1, "value": 1}, unit_id: 1}}},
{$group: {"_id": "$units.unit_id", "quarter_values": {$push: "$units.values"}}} ,
{$sort: {"_id": 1}}
])
Will give:
{
"_id" : 10951,
"quarter_values" : [
{
"quarter" : 1473324300,
"value" : 410
},
{
"quarter" : 1473325200,
"value" : 412
},
{
"quarter" : 1473326100,
"value" : 412
},
{
"quarter" : 1473327000,
"value" : 411
},
{
"quarter" : 1473327900,
"value" : 408
},
{
"quarter" : 1473328800,
"value" : 403
}
]
}
See: Return only matched sub-document elements within a nested array for a detailed description!
I think I have to switch to $map or $filter in the future. Thanks to notionquest for supporting my questions :)
Please see the sample query below. I didn't exactly get your grouping requirement. However, with this sample query you should be able to change and get your desired output.
db.collection.aggregate([
{$unwind : {path : "$units"}},
{$match : {$and : [{"units.values.quarter" : {$gte : 1473324300}}, {"units.values.quarter" : {$lte : 1473328800 }}]}},
{$project : {"units" : {values : {quarter : 1, "value" : 1}, unit_id : 1}}},
{$group : { _id : "$units.unit_id", quarter_values : { $push :{ quarter : "$units.values.quarter", value : "$units.values.value"}}}},
{$sort : {_id : 1 }}
]);
Sample output:-
{
"_id" : 10951,
"quarter_values" : [
{
"quarter" : [
1473321600,
1473322500,
1473323400,
1473324300
],
"value" : [
395,
402,
406,
410
]
}
]
}

MongoDB: $mod operator in aggregation pipeline

I have a restaurants collection that contains 3772 documents and I am trying to calculate the total number of documents that contain a score in first element of the grades array that's a multiple of 7 using the aggregation framework.
Query:
db.restaurants.aggregate([
{$project: {remainder: {$mod: ["$grades.0.score", 7]},
restaurant_id: 1,
name: 1,
grades: 1
}
},
{$match: {remainder: {$eq: 0}}},
{$group: {_id: null, total: {$sum: 1}}}
])
However, I am getting an error message that's caused by the use of the $mod operator in the $project pipeline stage. The error message is the following:
$mod only supports numeric types, not Array and NumberDouble
However, both $grades.0.score and 7 are integers, right? What should I change to make this query work as intended?
Example document:
{
"_id" : ObjectId("57290430139a4a37132c9e93"),
"address" : {
"building" : "469",
"coord" : [
-73.961704,
40.662942
],
"street" : "Flatbush Avenue",
"zipcode" : "11225"
},
"borough" : "Brooklyn",
"cuisine" : "Hamburgers",
"grades" : [
{
"date" : ISODate("2014-12-30T00:00:00Z"),
"grade" : "A",
"score" : 8
},
{
"date" : ISODate("2014-07-01T00:00:00Z"),
"grade" : "B",
"score" : 23
},
{
"date" : ISODate("2013-04-30T00:00:00Z"),
"grade" : "A",
"score" : 12
},
],
"name" : "Wendy'S",
"restaurant_id" : "30112340"
}
instead of $grades.0.score
put $grades[0].score
in your query.
the above is wrong. see below the correct form. As you want to filter by grades whose first score is a multiple of 7, you aggregation should start like this.
db.restaurants.aggregate([{$match: {"grades.0.score": {$mod: [7, 0]}}},{$group: {_id: null, total: {$sum: 1}}}])
I changed the grade.0.score to 7 and ran the command to check it is working or not, it seems it is working as you wanted.
> db.restaurants.find().pretty();
{
"_id" : 0,
"address" : {
"building" : "469",
"coord" : [
-73.961704,
40.662942
],
"street" : "Flatbush Avenue",
"zipcode" : "11225"
},
"borough" : "Brooklyn",
"cuisine" : "Hamburgers",
"grades" : [
{
"date" : ISODate("2014-12-30T00:00:00Z"),
"grade" : "A",
"score" : 7
},
{
"date" : ISODate("2014-07-01T00:00:00Z"),
"grade" : "B",
"score" : 23
},
{
"date" : ISODate("2013-04-30T00:00:00Z"),
"grade" : "A",
"score" : 12
}
],
"name" : "Wendy'S",
"restaurant_id" : "30112340"
> db.restaurants.aggregate([{$match: {"grades.0.score": {$mod: [7, 0]}}},{$group:{_id:null,count:{$sum:1}}} ])
{ "_id" : null, "count" : 1 }
First: why doesn't it work? Try:
db.restaurants.aggregate([
{$project: {
score0: "$grades.0.score",
restaurant_id: 1,
name: 1
}
}
])
You'll see that score0 returns [0 elements] so it does output an array hence the error message.
Based on this other question Get first element in array and return using Aggregate? (Mongodb), here is a solution to your problem:
db.restaurants.aggregate([
{$unwind: "$grades"},
{$group:{"_id":"$_id","grade0":{$first:"$grades"}}},
{$project: {
remainder: {$mod: ["$grade0.score", 7]},
restaurant_id: 1,
name: 1,
grade0: 1,
}
},
{$match: {remainder: {$eq: 0}}},
{$group: {_id: null, total: {$sum: 1}}}
])

MongoDB group by subdocument counts and year

Here is a sample of my document from collection called products:
{
"_id" : "B000KIT6LQ",
"brand" : "unknown",
"category" : "Electronics",
"price" : "11.99",
"title" : "Scosche KA2067B 2005..."
"reviews" : [
{
"date" : ISODate("1969-12-31T23:59:59Z"),
"score" : 5,
"user_id" : "AK7M5Y7E9O3L7",
"sentiment" : 0.5,
"text" : "Bought this so I ...",
"user_gender" : "female",
"voted_total" : 0,
"voted_helpful" : 0,
"user_name" : "Alex",
"summary" : "It is what it is"
},
{
"date" : ISODate("1969-12-31T23:59:59Z"),
"score" : 5,
"user_id" : "A26VRLMPEA8IDR",
"sentiment" : 0.352,
"text" : "Years ago I worked as an...",
"user_gender" : "male",
"voted_total" : 0,
"voted_helpful" : 0,
"user_name" : "Jack R. Smith",
"summary" : "Great Kit"
},
{
"date" : ISODate("1969-12-31T23:59:59Z"),
"score" : 4,
"user_id" : "A1TGBDVX3QXCRH",
"sentiment" : 0.19318181818181818,
"text" : "This insert works great in my ...",
"user_gender" : "female",
"voted_total" : 0,
"voted_helpful" : 0,
"user_name" : "J. Reed",
"summary" : "Fits great in my 2006 Spectra5"
}
]
}
I have many documents with multiple categories. I am trying to create a mongo query which will result in all categories with the number of reviews (subdocument) per year. I have to group by categories and year, and get the count for number of reviews.
This is the query that I have got so far:
db.products.aggregate([
{ $unwind : "$reviews" },
{ $group: {
_id: {category: "$category", date: "$reviews.date.getFullYear()"},
count: { $sum: 1 }}},
{$sort:{"count": -1}}
])
For some reason the getFullYear() method is not working for me. If I group by $reviews.date I get the results.
Any pointers on getting the query right is appreciated.
Thanks
You can't use JavaScript functions like getFullYear() in your aggregate pipeline, you need to use the equivalent aggregation Date operator, which in this case is $year.
db.products.aggregate([
{ $unwind : "$reviews" },
{ $group: {
_id: {category: "$category", date: {$year: "$reviews.date"}},
count: { $sum: 1 }}},
{$sort:{"count": -1}}
])