I am using an aggregation pipeline to aggregate stats for my game. My pipeline consists of first filtering all the games in the collection by the player's ObjectID, then summing their stats and analytics. The filtering is done by checking each array in a game's players array. The players array is an array of objects, and I check the uuid field on each object, to see if it corresponds with my target ObjectID.
Aggregating the stats works fine for simple $sum, but I am now attempting to do a more advanced sum. I want to get the average opponent rating. Each player has a team field of either 1 or 2, representing the possible teams. If the player's team is 1, I need to fetch team 2's rating, if their team is 2, I need to get team 1's rating. I designate team 1 as blue team, and team 2 and red team in my schema for simplicity. Here is an example game
{
"type": "Regular",
"map": "Classic",
"winningTeam": 1,
"gameStats": {
"duration": 7,
"redScore": 1,
"blueScore": 0,
"redRating": 1000,
"blueRating": 1000,
},
"players": [
{
"uuid": "ObjectId",
...
"stats": {
"timePlayed": 7,
"goals": 0,
"ownGoals": 0,
"goalsFor": 1,
"goalsAgainst": 0,
},
}
And here is my pipeline
[
{
$addFields: {
players: {
$filter: {
input: "$players",
as: "player",
cond: {
$eq: [
"$$player.uuid",
playerObjectId
],
},
},
},
},
},
{
$group: {
_id: playerObjectId,
oppRating: {
$avg: {
$avg: {
$switch: {
branches: [
{
case: {
$eq: [
"$players.team",
1
]
},
then: "$gameStats.blueRating"
},
{
case: {
$eq: [
"$players.team",
2
]
},
then: "$gameStats.redRating"
},
]
}
}
}
},
timePlayed: {
$sum: {
$sum: "$players.stats.timePlayed",
},
},
},
goals: {
$sum: {
$sum: "$players.stats.goals",
},
...
]
Now my $switch doesn't work, and I've identified the problem to be the fact that I cant access the $players field for some reason. For example when I set the condition to
$eq: [
1,
1
],
It will work, and correctly get the average. I see my issue is being able to access the $players variable that I set up in my addfields, why cant I access this variable in the $switch statement, but I can access it in all my other fields, like the $sum for timeplayed. Do I need to rethink my filter query? I understand that I could simply add a field to every playerObject that reads "opponentRating", but I would like to see if there is simply an aggregation way to do this first.
CodePudding user response:
players
must be an object to considered inside the $switch
block. Just need to add $unwind
after the addFields, since $filter
will return an array.
db.game.aggregate([
{
$addFields: {
players: {
$filter: {
input: "$players",
as: "player",
cond: {
$eq: [
"$$player.uuid",
playerObjectId
],
},
},
},
},
},
{
$unwind: '$players'
},
{
$group: {
_id: playerObjectId,
oppRating: {
$avg: {
$avg: {
$switch: {
branches: [
{
case: {
$eq: [
"$players.team",
1
]
},
then: "$gameStats.blueRating"
},
{
case: {
$eq: [
"$players.team",
2
]
},
then: "$gameStats.redRating"
},
]
}
}
}
},
timePlayed: {
$sum: {
$sum: "$players.stats.timePlayed",
},
},
goals: {
$sum: {
$sum: "$players.stats.goals",
}
}
}
}
])
Also, I thought some performance optimisations can be done & redundant functions could be removed on the pipeline such as
- Instead of
$filter
forplayers
, we can use$match
,$unwind
&$match
- one
$avg
will suffice foroppRating
- And, one
$sum
will suffice fortimePlayed
&goals
You can try the below pipeline
db.game.aggregate([
{
$match: {
'players.uuid': playerObjectId,
}
},
{
$unwind: '$players'
},
{
$match: {
'players.uuid': playerObjectId,
}
},
{
$group: {
_id: playerObjectId,
oppRating: {
$avg: {
$switch: {
branches: [
{
case: {
$eq: [
"$players.team",
1
]
},
then: "$gameStats.blueRating"
},
{
case: {
$eq: [
"$players.team",
2
]
},
then: "$gameStats.redRating"
},
]
}
}
},
timePlayed: {
$sum: "$players.stats.timePlayed",
},
goals: {
$sum: "$players.stats.goals",
}
}
}
])