Reputation: 529
I am recording golf scores and now trying to calculate the leaderboards. This is for use on a PHP site, so I also need to translate the pipeline code (afterwards!).
Each round
document has a sub-array of holes
:
{
"playerName": "Tiger Woods",
"comp": {
"name": "US Open 2021",
"courseId": "608952e3abebbd503ba6e115",
"scoringMethod": "Stableford",
"tees": "Blue",
"courseName": "MAGC Middle 9",
"roundNo": 2,
"id": {
"$oid": "607019361c071256e4f0d0d5"
}
},
"holes": [
{
"blue": 431,
"holeNett": 4,
"par": 4,
"holeGross": 5,
"no": 1,
"holeNettPoints": 2,
"yellow": 420,
"si": 2,
"holeStrokes": 1,
"white": 444,
"red": 399
},
{
"blue": 335,
"holeNett": 5,
"par": 4,
"holeGross": 5,
"no": 2,
"holeNettPoints": 1,
"yellow": 320,
"si": 8,
"holeStrokes": 0,
"white": 350,
"red": 295
},
{
"blue": 385,
"holeNett": 4,
"par": 4,
"holeGross": 5,
"no": 3,
"holeNettPoints": 2,
"yellow": 362,
"si": 4,
"holeStrokes": 1,
"white": 400,
"red": 343
},
{
"blue": 161,
"holeNett": 3,
"par": 3,
"holeGross": 3,
"no": 4,
"holeNettPoints": 2,
"yellow": 144,
"si": 7,
"holeStrokes": 0,
"white": 180,
"red": 105
},
{
"blue": 461,
"holeNett": 5,
"par": 5,
"holeGross": 6,
"no": 5,
"holeNettPoints": 2,
"yellow": 439,
"si": 1,
"holeStrokes": 1,
"white": 473,
"red": 418
},
{
"blue": 330,
"holeNett": 4,
"par": 4,
"holeGross": 4,
"no": 6,
"holeNettPoints": 2,
"yellow": 300,
"si": 9,
"holeStrokes": 0,
"white": 337,
"red": 281
},
{
"blue": 381,
"holeNett": 6,
"par": 4,
"holeGross": 6,
"no": 7,
"holeNettPoints": 0,
"yellow": 363,
"si": 5,
"holeStrokes": 0,
"white": 390,
"red": 290
},
{
"blue": 152,
"holeNett": 3,
"par": 3,
"holeGross": 3,
"no": 8,
"holeNettPoints": 2,
"yellow": 140,
"si": 6,
"holeStrokes": 0,
"white": 167,
"red": 131
},
{
"blue": 366,
"holeNett": 3,
"par": 4,
"holeGross": 4,
"no": 9,
"holeNettPoints": 3,
"yellow": 344,
"si": 3,
"holeStrokes": 1,
"white": 396,
"red": 327
}
],
"playerId": "609d0993906429612483cea0",
"holeCount": 9,
"countbackScores": [
16,
11,
5,
2
],
"team": "magc",
"date": "2021-05-20T23:00:00+00:00",
"computedScore": 16,
"computedThru": 9,
"_id": {
"$oid": "60a6aa828078924a6065dc9e"
}
}
I have an aggregation pipeline that finds the highest or lowest (competition rules dependent) score on every hole. I had to $unwind
the sub-array, producing a pipeline document per hole played. Typically this means either 18 documents or 9 for a half-round, per player. Also to note, each round
could be played on a different comp.courseId
(there are 3 courses in my initial comp) - so these need to be grouped by as well, hence it is currently in my $match
filter.
Can I in the same pipeline calculate the cumulative total of final pipeline output, at the same time as retaining the previous documents?
[{$match: {
"comp.id" : ObjectId('600019361c071256e4f0d0d5'),
"playerId" : "600d0993906429612483cea0",
"comp.courseId" : "600955aaabebbd503ba6e116"
}
},
{$unwind: {
path : "$holes"
}},
{$group: {
_id: "$holes.no",
hole: {
$max: "$holes"
}
}},
{$set: {
"total": { "$sum" : "$hole.holeNettPoints" }
}},
{$sort: {
"hole.no": 1
}
}]
The $set
pipeline is the stage I am testing at. But this only sums the holeNettPoints
field, from a single document, so is the same as this value in each document.
I don't think I can do another $group
of all these documents, as on my PHP page though, I need the other per-hole fields to build an HTML table. But its sorted by the cumulative total for the round. So I have tried adding a field with $set
. Is this correct?
This returns a document per hole played (9 with my current data, commonly 18 for a full round of golf). I would like to have the cumulative holeNettPoints
total for all 9 holes as well as the $max
per hole holeNettPoints
across each player on each courseId
. Something of the structure:
playerId = 123456
courseId = abcdef
total = 18
holes = (
([no] => 1, [holeNettPoints] => 2),
([no] => 2, [holeNettPoints] => 2),
([no] => 3, [holeNettPoints] => 3),
([no] => 4, [holeNettPoints] => 1),
([no] => 5, [holeNettPoints] => 2),
([no] => 6, [holeNettPoints] => 2),
([no] => 7, [holeNettPoints] => 2),
([no] => 8, [holeNettPoints] => 2),
([no] => 9, [holeNettPoints] => 2),
)
Ideally, the result would return a single document, and then I would be able to remove the playerId
$match
filter so that it would work across the many players who would play in each comp.
I would prefer that MongoDB uses aggregation to return the formatted and sorted result, so PHP can just loop through it's results.
Upvotes: 1
Views: 119
Reputation: 36114
$group
by playerId
, courseId
and no
and get max of holeNettPoints
$group
by playerId
and construct the array of holes
with no
and holeNettPoints
and also get total of all holeNettPoints
db.collection.aggregate([
{
$match: {
"comp.id": ObjectId("607019361c071256e4f0d0d5"),
"playerId": "609d0993906429612483cea0",
"comp.courseId": "608952e3abebbd503ba6e115"
}
},
{ $unwind: { path: "$holes" } },
{
$group: {
_id: {
playerId: "$playerId",
courseId: "$comp.courseId",
no: "$holes.no"
},
holeNettPoints: { $max: "$holes.holeNettPoints" }
}
},
{
$group: {
_id: "$_id.playerId",
courseId: { $first: "$_id.courseId" },
holes: {
$push: {
no: "$_id.no",
holeNettPoints: "$holeNettPoints"
}
},
total: { $sum: "$holeNettPoints" }
}
}
])
PHP syntax:
$query = [
[
'$match' => [
'comp.id' => ObjectId("607019361c071256e4f0d0d5"),
'playerId' => "609d0993906429612483cea0",
'comp.courseId' => "608952e3abebbd503ba6e115"
]
],
[ '$unwind' => [ 'path' => '$holes' ] ],
[
'$group' => [
'_id' => [
'playerId' => '$playerId',
'courseId' => '$comp.courseId',
'no' => '$holes.no'
],
'holeNettPoints' => [ '$max' => '$holes.holeNettPoints' ]
]
],
[
'$group' => [
'_id' => '$_id.playerId',
'courseId' => [ '$first' => '$_id.courseId' ],
'holes' => [
'$push' => [
'no' => '$_id.no',
'holeNettPoints' => '$holeNettPoints'
]
],
'total' => [ '$sum' => '$holeNettPoints' ]
]
]
]
Upvotes: 2