1

I'm trying to track daily stats for an individual.

I'm having a hard time adding a new day inside "history" and can also use a pointer on updating "walkingSteps" as new data comes in.

My schema looks like:

{
"_id": {
    "$oid": "50db246ce4b0fe4923f08e48"
},
"history": [
    {
        "_id": {
            "$oid": "50db2316e4b0fe4923f08e12"
        },
        "date": {
            "$date": "2012-12-24T15:26:15.321Z"
        },
        "walkingSteps": 10,
        "goalStatus": 1
    },
    {
        "_id": {
            "$oid": "50db2316e4b0fe4923f08e13"
        },
        "date": {
            "$date": "2012-12-25T15:26:15.321Z"
        },
        "walkingSteps": 5,
        "goalStatus": 0
    },
    {
        "_id": {
            "$oid": "50db2316e4b0fe4923f08e14"
        },
        "date": {
            "$date": "2012-12-26T15:26:15.321Z"
        },
        "walkingSteps": 8,
        "goalStatus": 0
    }
]

}

db.history.update( ? )

I've been browsing (and attempting) the mongodb documentation but they don't quite break it all the way down to dummies like myself... I couldn't quite translate their examples to my setup.

Thanks for any help.

E = noob trying to learn programming

7
  • What exactly are the queries you wish to perform on walkingsteps when you add a new day? Commented Jan 6, 2013 at 17:58
  • When adding a new day walkingSteps and goalStatus should both be 0. Commented Jan 6, 2013 at 18:01
  • Say at lunchtime I upload my data. I'd like to update walkingSteps to the current total I've accumulated for today. Is that what you're looking for? Commented Jan 6, 2013 at 18:01
  • Hmm, I am a bit confused, ok so does history represent days or does it represent time of day? I am assuming by accumulated you mean the amount of history items for that day? Commented Jan 6, 2013 at 18:03
  • History represents my entire lifetime of days. So in this example there are 3 days listed inside history. (It's likely I have setup the schema awkwardly it seems). Yes, the amount of history items for that day. Commented Jan 6, 2013 at 18:07

3 Answers 3

5

Adding a day:

user = {_id: ObjectId("50db246ce4b0fe4923f08e48")}
day = {_id: ObjectId(), date: ISODate("2013-01-07"), walkingSteps:0, goalStatus: 0}
db.users.update(user, {$addToSet: {history:day}})

Updating walkingSteps:

user = ObjectId("50db246ce4b0fe4923f08e48")
day = ObjectId("50db2316e4b0fe4923f08e13") // second day in your example
query = {_id: user, 'history._id': day}

db.users.update(query, {$set: {"history.$.walkingSteps": 6}})

This uses the $ positional operator.

It might be easier to have a separate history collection though.


[Edit] On the separate collections:

  • Adding days grows the document in size and it might need to be relocated on the disk. This can lead to performance issues and fragmentation.
  • Deleting days won't shrink the document size on disk.
  • It makes querying easier/straightforward (e.g. searching for a period of time)
Sign up to request clarification or add additional context in comments.

11 Comments

That worked perfectly, thanks! Can you expound on the $ positional character? Is it because you don't know which line # (per se) is walkinSteps in the brackets... within the _id, date, goalStatus...?
Also what are your suggestions on a separate history collection? Reason being? What am I doing wrong?
Exactly. "The positional $ operator, when used with the update() method acts as a placeholder for the first match of the update query selector". I'll edit my answer to expand on the separate collection.
Deleting and then inserting documents that do not fit into those free extents will cause fragmentation but updating will merely reallocate the extent in which the document resides (or should, there have been a few bugs but I think they are fixed). I am unsure about the lack of size shrink on a document if you shrink it. The aggregation framework could easily make the querying of data in this array really fast
The freed up space that a moved doc leaves behind only allows for a properly sized document though? With many users with different number of history days that might be problematic. Doing a repair on the DB is afaik the only way to compact documents that have shrunk.
|
1

Even though @Justin Case puts the right answer he doesn't explain a few things in it extremely well.

You will notice first of all that he gets rid of the resolution on dates and moves their format to merely the date instead of date and time like so:

day = {_id: ObjectId(), date: ISODate("2013-01-07"), walkingSteps:0, goalStatus: 0}

This means that all your dates will have 00:00:00 for their time instead of the exact time you are using atm. This increases the ease of querying per day so you can do something like:

db.col.update(
    {"_id": ObjectId("50db246ce4b0fe4923f08e48"), 
    "history.date": ISODate("2013-01-07")}, 
    {$inc: {"history.$.walkingSteps":0}}
)

and other similar queries.

This also makes $addToSet actually enforce its rules, however since the data in this sub document could change, i.e. walkingSteps will increment $addToSet will not work well here anyway.

This is something I would change from the ticked answer. I would probably use $push or something else instead since $addToSet is heavier and won't really do anything useful here.

The reason for a separate history collection in my view would be what you said earlier with:

Yes, the amount of history items for that day.

So this array contains a set of days, which is fine but it sounds like the figure that you wish to get walkingSteps from, a set of history items, should be in another collection and you set walkingSteps according to the count of the amount of items in that other collection for today:

db.history_items.find({date: ISODate("2013-01-07")}).count();

4 Comments

$push will add duplicate entries for the same day though which isn't necessarily what OP wants. If the add-day query will never be called twice then I agree that $push is faster.
@JustinCase If he increments or changes walkingSteps at all which he intends to do then $addToSet will readd that element too, $addToSet evaluates the entire array element being added, he probably should be $pushing where the day does not already exist.
I'm trying to automate adding a day, say at midnight so it'll only need to happen once for the given day. Then updating "walkingSteps" and "goalStatus" values during that day.
@user191277 $push will do just fine here since you are only running the query once (at midnight).
0

Referring to MongoDB Manual, $ is the positional operator which identifies an element in an array field to update without explicitly specifying the position of the element in the array. The positional $ operator, when used with the update() method and acts as a placeholder for the first match of the update query selector.

So, if you issue a command to update your collection like this:

db.history.update(
   {someCriterion: someValue },
   { $push: { "history": 
               {"_id": {
                   "$oid": "50db2316e4b0fe4923f08e12"
               },
              "date": {
                   "$date": "2012-12-24T15:26:15.321Z"
               },
           "walkingSteps": 10,
           "goalStatus": 1
           } 
   } 
)

Mongodb might try to identify $oid and $date as some positional parameters. $ also is part of the atomic operators like $set and $push. So, it is better to avoid use this special character in Mongodb.

5 Comments

Ok. I may slowly be catching on. So if just using $ to target an element isn't the best, how would you convert this db.users.update(query, {$set: {"history.$.walkingSteps": 6}}) to be handled better?
@user191277 Hmm not sure if what above makes sense, the $date and $oid are BSON types and handled by MongoDB properly, the positional operator will work just fine here.
OP's schema (with $oid and $date) is just how the data is represented by the driver e.g. PHP shows it as { ["$id"]=> string(24) "50e6427cbd99e40f2a000000" }.
@JustinCase I got your point, thanks. In this case the OP should indicate the specific driver. And generally it is not a good practice to use '$' as part of field name.
@user191277 I mean '$' is a special character in mongodb so try to avoid using it in field names.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.