Sunday, February 14, 2016

MongoDB Update Operations

In last post we introduced three of the four CRUD (create, read, update and delete) operations. This post  is dedicated to the one we skipped over: update. Update has a few surprising behaviours, which is why we dedicate a chapter to it.

Update: Replace Versus $set
In its simplest form, update takes two parameters: the selector (where) to use and what updates to apply to fields. If Roooooodles had gained a bit of weight, you might expect that we should execute:
> db.unicorns.find({name:'Roooooodles'}).pretty()
{
        "_id" : ObjectId("56bf377510a4febbd2830cff"),
        "name" : "Roooooodles",
        "dob" : ISODate("1979-08-18T13:14:00Z"),
        "loves" : [
                "apple"
        ],
        "weight" : 575,
        "gender" : "m",
        "vampires" : 99
}

> db.unicorns.update({name: 'Roooooodles'},{weight: 590})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.unicorns.find({name:'Roooooodles'}).pretty()
>
> db.unicorns.find({weight:590}).pretty()
{ "_id" : ObjectId("56bf377510a4febbd2830cff"), "weight" : 590 }

You should discover the first surprise of update. No document is found because the second parameter we supplied didn’t have any update operators, and therefore it was used to replace the original document. In other words, the update found a document by name and replaced the entire document with the new document (the second parameter).
There is no equivalent functionality to this in SQL’s update command. In some situations, this is ideal and can be leveraged for some truly dynamic updates. However, when you want to change the value of one, or a few fields, you must use MongoDB’s $set operator. Go ahead and run this update to reset the lost fields:

db.unicorns.update({weight: 590}, {$set: {
name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
gender: 'm',
vampires: 99}})

This won’t overwrite the new weight since we didn’t specify it. Now if we execute:
db.unicorns.find({name: 'Roooooodles'})
{
        "_id" : ObjectId("56bf377510a4febbd2830cff"),
        "weight" : 590,
        "name" : "Roooooodles",
        "dob" : ISODate("1979-08-18T13:14:00Z"),
        "loves" : [
                "apple"
        ],
        "gender" : "m",
        "vampires" : 99
}

We get the expected result. Therefore, the correct way to have updated the weight in the first place is:
db.unicorns.update({name: 'Roooooodles'},
{$set: {weight: 590}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

Update Operators

In addition to $set, we can leverage other operators to do some nifty things. All update operators work on fields - so your entire document won’t be wiped out. For example, the $inc operator is used to increment a field by a certain positive or negative amount. If Pilot was incorrectly awarded a couple vampire kills, we could correct the mistake by executing:

> db.unicorns.find({name:'Aurora'}).pretty();
{
        "_id" : ObjectId("56bf377510a4febbd2830cfd"),
        "name" : "Aurora",
        "dob" : ISODate("1991-01-24T07:30:00Z"),
        "loves" : [
                "carrot",
                "grape"
        ],
        "weight" : 450,
        "gender" : "f",
        "vampires" : 43
}

> db.unicorns.update({name:'Aurora'},{$inc:{vampires:-2}});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1

> db.unicorns.find({name:'Aurora'}).pretty();
{
        "_id" : ObjectId("56bf377510a4febbd2830cfd"),
        "name" : "Aurora",
        "dob" : ISODate("1991-01-24T07:30:00Z"),
        "loves" : [
                "carrot",
                "grape"
        ],
        "weight" : 450,
        "gender" : "f",
        "vampires" : 41
}

If Aurora suddenly developed a sweet tooth, we could add a value to her loves field via the $push operator:
> db.unicorns.update({name:'Aurora'},{$push:{loves:'sugar'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
> db.unicorns.find({name:'Aurora'}).pretty();
{
        "_id" : ObjectId("56bf377510a4febbd2830cfd"),
        "name" : "Aurora",
        "dob" : ISODate("1991-01-24T07:30:00Z"),
        "loves" : [
                "carrot",
                "grape",
                "sugar"
        ],
        "weight" : 450,
        "gender" : "f",
        "vampires" : 41
}
>

Upserts

One of the more pleasant surprises of using update is that it fully supports upserts. An upsert updates the document if found or inserts it if not. Upserts are handy to have in certain situations and when you run into one, you’ll know it. 
To enable upserting we pass a third parameter to update {upsert:true}.

A mundane example is a hit counter for a website. If we wanted to keep an aggregate count in real time, we’d have to see if the record already existed for the page, and based on that decide to run an update or insert. With the upsert option omitted (or set to false), executing the following won’t do anything:
db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}});
db.hits.find();

However, if we add the upsert option, the results are quite different:
db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}}, {upsert:true});
db.hits.find();

> db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}},{upsert:true});
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("56c0bd2db0365a31218b1d58")

> db.hits.find().pretty()
{
        "_id" : ObjectId("56c0bd2db0365a31218b1d58"),
        "page" : "unicorns",
        "hits" : 1

Since no documents exists with a field page equal to unicorns, a new document is inserted. If we execute it a second time, the existing document is updated and hits is incremented to 2.

> db.hits.update({page: 'unicorns'}, {$inc: {hits: 1}},{upsert:true});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.hits.find().pretty()
{
        "_id" : ObjectId("56c0bd2db0365a31218b1d58"),
        "page" : "unicorns",
        "hits" : 2


Multiple Updates

The final surprise update has to offer is that, by default, it’ll update a single document. So far, for the examples we’ve looked at, this might seem logical. However, if you executed something like:
db.unicorns.update({}, {$set: {vaccinated: true }});
db.unicorns.find({vaccinated: true});

You might expect to find all of your precious unicorns to be vaccinated.

Generally when we perform an update statement it will update only one or the first document in the list. Taking example of updating the weight of all records where weight is above 700 and increasing it by 1.56.

Displaying the list of records where weight is greater than 700.

> db.unicorns.find({weight:{$gt:700}}).pretty()
{
        "_id" : ObjectId("56bf377510a4febbd2830cfe"),
        "name" : "Unicrom",
        "dob" : ISODate("1973-02-09T16:40:00Z"),
        "loves" : [
                "energon",
                "redbull"
        ],
        "weight" : 984,
        "gender" : "m",
        "vampires" : 182
}
{
        "_id" : ObjectId("56bf378810a4febbd2830d01"),
        "name" : "Ayna",
        "dob" : ISODate("1998-03-07T03:00:00Z"),
        "loves" : [
                "strawberry",
                "lemon"
        ],
        "weight" : 733,
        "gender" : "f",
        "vampires" : 40
}
{
        "_id" : ObjectId("56bf378910a4febbd2830d07"),
        "name" : "Dunx",
        "dob" : ISODate("1976-07-18T12:48:00Z"),
        "loves" : [
                "grape",
                "watermelon"
        ],
        "weight" : 704,
        "gender" : "m",
        "vampires" : 165
}

> db.unicorns.update({weight:{$gt:700}},{$inc:{weight:1.56}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.unicorns.find({weight:{$gt:700}}).pretty()
{
        "_id" : ObjectId("56bf377510a4febbd2830cfe"),
        "name" : "Unicrom",
        "dob" : ISODate("1973-02-09T16:40:00Z"),
        "loves" : [
                "energon",
                "redbull"
        ],
        "weight" : 985.56,
        "gender" : "m",
        "vampires" : 182
}
{
        "_id" : ObjectId("56bf378810a4febbd2830d01"),
        "name" : "Ayna",
        "dob" : ISODate("1998-03-07T03:00:00Z"),
        "loves" : [
                "strawberry",
                "lemon"
        ],
        "weight" : 733,
        "gender" : "f",
        "vampires" : 40
}
{
        "_id" : ObjectId("56bf378910a4febbd2830d07"),
        "name" : "Dunx",
        "dob" : ISODate("1976-07-18T12:48:00Z"),
        "loves" : [
                "grape",
                "watermelon"
        ],
        "weight" : 704,
        "gender" : "m",
        "vampires" : 165
}

As you can see, only the first record has been updated. 
To get the behaviour you desire, the multi option must be set to true:

> db.unicorns.update({weight:{$gt:700}},{$inc:{weight:1.56}},{multi:true})
WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 3 })
>
> db.unicorns.find({weight:{$gt:700}}).pretty()
{
        "_id" : ObjectId("56bf377510a4febbd2830cfe"),
        "name" : "Unicrom",
        "dob" : ISODate("1973-02-09T16:40:00Z"),
        "loves" : [
                "energon",
                "redbull"
        ],
        "weight" : 988.6799999999998,
        "gender" : "m",
        "vampires" : 182
}
{
        "_id" : ObjectId("56bf378810a4febbd2830d01"),
        "name" : "Ayna",
        "dob" : ISODate("1998-03-07T03:00:00Z"),
        "loves" : [
                "strawberry",
                "lemon"
        ],
        "weight" : 734.56,
        "gender" : "f",
        "vampires" : 40
}
{
        "_id" : ObjectId("56bf378910a4febbd2830d07"),
        "name" : "Dunx",
        "dob" : ISODate("1976-07-18T12:48:00Z"),
        "loves" : [
                "grape",
                "watermelon"
        ],
        "weight" : 705.56,
        "gender" : "m",
        "vampires" : 165
}

The previously shown command will be handled this way:
db.unicorns.update({},
{$set: {vaccinated: true }}, {multi:true});
db.unicorns.find({vaccinated: true});

1 comment:

  1. How to Choose Best Slot Machines - Mapyro
    The most common slot machines with 김천 출장마사지 payouts are Pragmatic Play 서울특별 출장샵 slots that 나주 출장마사지 don't 공주 출장안마 pay out when 이천 출장마사지 playing them. In addition, slot games

    ReplyDelete

Mongodb explain() Query Analyzer and it's Verbosity

First creating 1 million documents: > for(i=0; i<100; i++) { for(j=0; j<100; j++) {x = []; for(k=0; k<100; k++) { x.push({a:...