Retrieve highest score for each game using aggregate in MongoDB - mongodb

I am working on a database of various games and i want to design a query that returns top scorer from each game with specific player details.
The document structure is as follows:
db.gaming_system.insertMany(
[
{
"_id": "01",
"name": "GTA 5",
"high_scores": [
{
"hs_id": 1,
"name": "Harry",
"score": 6969
},
{
"hs_id": 2,
"name": "Simon",
"score": 8574
},
{
"hs_id": 3,
"name": "Ethan",
"score": 4261
}
]
},
{
"_id": "02",
"name": "Among Us",
"high_scores": [
{
"hs_id": 1,
"name": "Harry",
"score": 926
},
{
"hs_id": 2,
"name": "Simon",
"score": 741
},
{
"hs_id": 3,
"name": "Ethan",
"score": 841
}
]
}
]
)
I have created a query using aggregate which returns the name of game and the highest score for that game as follows
db.gaming_system.aggregate(
{ "$project": { "maximumscore": { "$max": "$high_scores.score" }, name:1 } },
{ "$group": { "_id": "$_id", Name: { $first: "$name" }, "Highest_Score": { "$max": "$maximumscore" } } },
{ "$sort" : { "_id":1 } }
)
The output from my query is as follows:
{ "_id" : "01", "Name" : "GTA 5", "Highest_Score" : 8574 }
{ "_id" : "02", "Name" : "Among Us", "Highest_Score" : 926 }
I want to generate output which also provides the name of player and "hs_id" of that player who has the highest score for each game as follows:
{ "_id" : "01", "Name" : "GTA 5", "Top_Scorer" : "Simon", "hs_id": 2, "Highest_Score" : 8574 }
{ "_id" : "02", "Name" : "Among Us", "Top_Scorer" : "Harry", "hs_id": 1, "Highest_Score" : 926 }
What should be added to my query using aggregate pipeline?

[
{
$unwind: "$high_scores" //unwind the high_scores, so you can then sort
},
{
$sort: {
"high_scores.score": -1 //sort the high_scores, irrelevant of game, because we are going to group in next stage
}
},
{
//now group them by _id, take the name and top scorer from $first (which is the first in that group as sorted by score in descending order
$group: {
_id: "$_id",
name: {
$first: "$name"
},
Top_Scorer: {
$first: "$high_scores"
}
}
}
]

Related

Is it possible to aggregate $$ROOT in mongo db?

I have following Mongo Collection.
[
{
"query": "a",
"page": "p1",
"clicks": 10,
"date": "x"
},
{
"query": "b",
"page": "p1",
"clicks": 5,
"date": "x"
},
{
"query": "a",
"page": "p1",
"clicks": 5,
"date": "y"
},
{
"query": "c",
"page": "p2",
"clicks": 2,
"date": "y"
},
]
Output Should be like this :
[
{
"page" : "p1",
"most_clicks_query" : "a",
"sum_of_clicks_for_query" : 15
},
{
"page" : "p2",
"most_clicks_query" : "c",
"sum_of_clicks_for_query" : 2
},
]
Logic to get this Output :
I need the query name that has most clicks for each page with sum of clicks (for that query)
What I ask :
I am hoping to get this result in one aggregation query.
So I am playing with $$ROOT.
In this path, now I am stuck with grouping the $$ROOT (to get sum of clicks for queries).
Can someone guide me a better path to do this?
Here is the aggregation you're looking for:
db.collection.aggregate([
{
"$group": {
"_id": {
"page": "$page",
"query": "$query"
},
"sum_of_clicks_for_query": {
"$sum": "$clicks"
}
}
},
{
"$project": {
"_id": false,
"page": "$_id.page",
"most_clicks_query": "$_id.query",
"sum_of_clicks_for_query": true
}
},
{
$sort: {
"sum_of_clicks_for_query": -1
}
},
{
$group: {
_id: "$page",
group: {
$first: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: "$group"
}
}
])
Playground: https://mongoplayground.net/p/Uzk3CuSwVRM

How to group by one matching element in a subdocument - Mongodb

Suppose I have the following collection.
[
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
},
{
"#pid": "61",
"text": "Mango"
},
{
"#pid": "92",
"text": "cherry"
},
{
"#pid": "27",
"text": "grape"
},
{
"#pid": "34",
"text": "dragonfruit"
}
]
},
"type": "A"
},
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
},
{
"#pid": "27",
"text": "grape"
},
{
"#pid": "34",
"text": "dragonfruit"
}
]
},
"type": "B"
},
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
}
]
},
"type": "A"
}
]
I want to get the type in which apple or mango is sold, group by item name. For the above collection, the output would be :
{
"_id": "Apple",
"items" : [
"A",
"B",
"A"
]
},
{
"_id": "Mango",
"items" : [
"A"
]
}
I tried the following query but it return nothing :
db.collection.aggregate([
{
$match : {
'items.item.text' : {$regex : 'Apple|Mango'}
}
},
{
$project : {
type : "$type"
}
},
{
$group : {
_id : '$items.item',
types : {$push : '$type'}
}
}
])
I think that even if this works, it's going to group by the entire 'items.item'. Where am I going wrong?
P.S. : I don't have the liberty to change the format of the document
Thanks a lot in advance.
You were on the right direction. You need to use $unwind operator and you don't need $project stage in your aggregation. The below query will be helpful:
db.collection.aggregate([
{
$unwind: "$items.item"
},
{
$match: {
"items.item.text": {
$regex: "Apple|Mango"
}
}
},
{
$group: {
_id: "$items.item.text",
type: {
$push: "$type"
}
}
}
])
MongoPlayGroundLink

Use the aggregate method with $unwind to parse out the scores array, followed by a $group and a $sum

What I'm trying to do is add up the sum of the scores for any German Restaurant in Manhattan. I want to return the top 5 restaurants with their name and total score.
Here is the setup of the json data I'm working with:
{
"address": {
"building": "1007",
"coord": [ -73.856077, 40.848447 ],
"street": "Morris Park Ave",
"zipcode": "10462"
},
"borough": "Bronx",
"cuisine": "Bakery",
"grades": [
{ "date": { "$date": 1393804800000 }, "grade": "A", "score": 2 },
{ "date": { "$date": 1378857600000 }, "grade": "A", "score": 6 },
{ "date": { "$date": 1358985600000 }, "grade": "A", "score": 10 },
{ "date": { "$date": 1322006400000 }, "grade": "A", "score": 9 },
{ "date": { "$date": 1299715200000 }, "grade": "B", "score": 14 }
],
"name": "Morris Park Bake Shop",
"restaurant_id": "30075445"
}
I've tried multiple variations of this:
db.restaurants.aggregate([{$unwind: "$grades"}, {$group: {_id: {borough: "Manhattan", cuisine:"German"}, total:{$sum:"scores"}}}])
And this is what keeps getting returned. I'm new to MongoDb and I'm just not sure what I'm doing wrong.
{ "_id" : { "borough" : "Manhattan", "cuisine" : "German" }, "total" : 0 }
So here is an aggregation that might work assuming the average score is what is measured...
Query:
db.restaurants.aggregate([
{ $match: { "borough": "Manhattan", "cuisine": "German" } },
{ $unwind: "$grades" },
{ $group: {
"_id": "$name",
"avg_score": { $avg: "$grades.score" }
}
},
{ $sort: { "avg_score": -1 } },
{ $limit: 5 }
]).pretty()
Pipeline Explaination:
match on borough and cuisine to filter to only desirable conditions
Unwind the grades array to flatten structure for review
group on the restaurant name and get the average score
Sort on the average score descending to get the top scores at the
top of the list
Limit output to the top 5 restaurants.
EDIT:
Sort by sum instead of average...
db.restaurants.aggregate([
{ $match: { "borough": "Manhattan", "cuisine": "German" } },
{ $unwind: "$grades" },
{ $group: {
"_id": "$name",
"avg_score": { $avg: "$grades.score" },
"sum_score": { $sum: "$grades.score" }
}
},
{ $sort: { "sum_score": -1 } },
{ $limit: 5 }
]).pretty()

group first, make bucketauto second in mongodb aggregation

I have a dataset structured like that:
{
"id": 1230239,
"group_name": "A",
"confidence": 0.14333882876354542,
},
{
"id": 1230240,
"group_name": "B",
"confidence": 0.4434535,
},
Etc.
It is pretty simple to calculate buckets and number of items in each bucket of confidence level, using $bucketauto like that:
{
"$bucketAuto": {
"groupBy": "$confidence",
"buckets": 4
}
}
But how can I do the same for each group, separately?
I tried this one:
{"$group": {
"_id": "group",
"data": {
"$push": {
"confidence": "$confidence",
}
}
}
},
{
"$bucketAuto": {
"groupBy": "$data.confidence",
"buckets": 4
}
}
But that does not work.
What I need roughly is this as an output:
{ 'groupA':
{
"_id": {
"min": 0.0005225352581638143,
"max": 0.2905137273072962
},
"count": 67
},
{"_id": {
"min": 0.2905137273072962,
"max":0.5531611756507283,
},
"count": 43
},
},
{ 'groupB':
{
"_id": {
"min": 0.0005225352581638143,
"max": 0.2905137273072962
},
"count": 67
},
{"_id": {
"min": 0.2905137273072962,
"max":0.5531611756507283,
},
"count": 43
},
}
Any advice or hint would be appreciated
$facet to the rescue -- the "multigroup" operator. This pipeline:
db.foo.aggregate([
{$facet: {
"groupA": [
{$match: {"group_name": "A"}}
,{$bucketAuto: {
"groupBy": "$confidence",
"buckets": 3
}}
]
,"groupB": [
{$match: {"group_name": "B"}}
,{$bucketAuto: {
"groupBy": "$confidence",
"buckets": 3
}}
]
}}
]);
yields the output you seek:
{
"groupA" : [
{
"_id" : {
"min" : 0.14333882876354542,
"max" : 0.34333882876354543
},
"count" : 2
},
{
"_id" : {
"min" : 0.34333882876354543,
"max" : 0.5433388287635454
},
"count" : 2
},
{
"_id" : {
"min" : 0.5433388287635454,
"max" : 0.5433388287635454
},
"count" : 1
}
],
"groupB" : [
{
"_id" : {
"min" : 0.5433388287635454,
"max" : 0.7433388287635454
// etc. etc.
If you want to go totally dynamic, you'll need to do it in two passes: first get the distinct group names, then build the $facet expression from those names:
db.foo.distinct("group_name").forEach(function(name) {
fct_stage["group" + name] = [
{$match: {"group_name": name}}
,{$bucketAuto: {
"groupBy": "$confidence",
"buckets": 3
}}
];
});
db.foo.aggregate([ {$facet: fct_stage} ]);

mongo aggregation framework group by quarter/half year/year

I have a database with this schema structure :
{
"name" : "Carl",
"city" : "paris",
"time" : "1-2018",
"notes" : [
"A",
"A",
"B",
"C",
"D"
]
}
And this query using the aggregation framework :
db.getCollection('collection').aggregate(
[{
"$match": {
"$and": [{
"$or": [ {
"time": "1-2018"
}, {
"time": "2-2018"
} ]
}, {
"name": "Carl"
}, {
"city": "paris"
}]
}
}, {
"$unwind": "$notes"
}, {
"$group": {
"_id": {
"notes": "$notes",
"time": "$time"
},
"count": {
"$sum": 1
}
}
}
, {
"$group": {
"_id": "$_id.time",
"count": {
"$sum": 1
}
}
}, {
"$project": {
"_id": 0,
"time": "$_id",
"count": 1
}
}])
It working correcly and i'm getting these results these results :
{
"count" : 4.0,
"time" : "2-2018"
}
{
"count" : 4.0,
"time" : "1-2018"
}
My issue is that i'd like to keep the same match stage and i'd like to group by quarter.
Here the result i'd like to have :
{
"count" : 8.0,
"time" : "1-2018" // here quarter 1
}
Thanks