Reputation: 358
I have a MongoDB collection, named Players
_id, name, stats[ { stName, stValue, stCountC } ]
I will be updating various stats for each Player every day, but will only update the Stat values when a new value is found (ie, new.stCount > existing.stCountC
). My general idea is to limit the stats array to 2 objects for each unique stName
-- one for the Current value and one for the Previous value. By example therefore, let's say I find a new stat with count of 6, my query and update process would start with -
db.players.findOne({ name: "JSmith",
stats: { $elemMatch: { stName: "Avg", stCountC: 5 } } })
If the above query returns a document, then I update as follows -
1) Get JSmith's stValue
where stName = "Avg"
and stCountP = 4
-
db.players.findOne({ name: "JSmith", stats: { stName: "Avg", stCountP: 4 } },
{ stats.$.stValue })
2) Insert this value to my StatsHistory
collection, which holds all historical values for each player for a single stat type -
db.statshistory.update({ Name: "JSmith", StatName: "Avg" },
{ $addToSet : { oldValues : { stValue: <val>, stCount: 4 } } })
3) Update my Players
collection -
db.players.update({ Name: JSmith },
{ $push: { stats: { stName: "Avg", stValue: "98", stCountC: 6 } }
$pull: { stats: { stName: "Avg", stCountP: 4 } } })
db.players.update({ name: "JSmith", stats.stName: "Avg", stats.stCountC: 5 },
{ $rename: { "stats.stCountC": "stats.stCountP" } })
I will be presenting data grids of current stat values from the Players collection (ie one row per player, one column per stat name). I will also have views that show trends of stat values, and assume I would use MongoDB's Aggregation functions to return these from my StatsHistory collection.
Question: Does the above data model and find/update process seem logical?
I'm obviously new to MongoDB, so apologies for any syntax errors or if the above seems totally inappropriate to my needs. Any feedback is much appreciated!
Resolved:
Thanks to idbentley for the suggestion below. It helped me devise the following database update procedure using async.js. Note the data model changed slightly -- the Players
collection now only holds the most current stat values, each attempted update pass updates a LastScan
date stamp on the main record, and each stat update provides a date stamp LastUpdate
. Stat count is still used to check if returned stat is newer. This procedure also ensures that any new players / stats are upserted to the collections.
async.series([
function(cb){ db.statshistory.update({ Name: <name>, StatName: <statname> },
{ $set : { LastScan: new Date() }},
{ upsert: true },
function() { cb(); });
}
,function(cb){ db.statshistory.update({ Name: <name>, StatName: <statname>, OldValues: { $not : { $elemMatch : { StCount: <statcount> }}}},
{ $push : { OldValues: { LastUpdate: new Date(), StCount: <statcount>, StValue: <statvalue> }}},
function() { cb(); });
}
,function(cb){ db.players.update({ Name: <name> },
{ $set : { LastScan: new Date() }},
{ upsert: true },
function() { cb(); });
}
,function(cb){ db.players.update({ Name: <name> },
{ $pull : { Stats: { StName: <statname>, StCount: { $ne: <statcount> }}}},
function() { cb(); });
}
,function(cb){ db.players.update({ Name: <name>, Stats: { $not : { $elemMatch : { StName: <statname>, StCount: <statcount> }}}},
{ $push : { Stats: { LastUpdate: new Date(), StCount: <statcount>, StValue: <statvalue> }}},
function() { cb(); });
}]
,function() { console.log('update complete'); }
)
Upvotes: 0
Views: 600
Reputation: 4218
I think you might be making things more complicated than necessary.
My understanding of your requirements:
If I understand correctly, then I would change things slightly.
First, I would add a current
flag to the embedded stats
documents.
Then your workflow changes to:
db.statshistory.update( { Name: <name>, StatName: <statname> },
{ $addToSet : { oldValues : { <statobj> } } } );
db.player.update( { Name: <name>, stats:
{ $elemMatch: { stName: <statname>} } },
{ $pull: { current: false } } );
db.player.update( {Name: <name>, stats:
{ $elemMatch: { stName: <statname>, current: true } } },
{ $set: { stats.current: false } } );
db.player.update({Name: <name>},
{ $addToSet: { <statobj>, current: true } });
Because this uses multiple updates, it won't work well in a multi-threaded environment.
Upvotes: 1