Showing posts with label mongo. Show all posts
Showing posts with label mongo. Show all posts

Wednesday, October 1, 2014

MongoDB Aggregation

Aggregation

Basic read operations are fine, but what about looking at the documents and computing results? To do that, we'll need to use aggregations.

First off, let's seed our database with some data we can aggregate.

var i = 40;
var names = 'George|Samuel|Thomas|Benjamin|John'.split('|');
var colors = 'Red|Orange|Yellow|Blue|Green|Indigo|Violet'.split('|');

while (i--) {
    db.people.insert({
        name: names[i % names.length],
        number: i,
        color: colors[i % colors.length],
        rand: Math.random()
    });
}

Whet Your Whistle

The aggregation docs are pretty deep, especially when you're just looking for a simple answer. So, to keep up the motivation, here's a quick taste of grouping.

db.people.aggregate([
    {
        $group: {
            _id: '$color',
            count: {
                $sum: 1
            }
        }     
    }
]);
{
    "result" : [
        {
            "_id" : "Red",
            "count" : 6
        },
        {
            "_id" : "Indigo",
            "count" : 5
        },
        {
            "_id" : "Violet",
            "count" : 5
        },
        {
            "_id" : "Orange",
            "count" : 6
        },
        {
            "_id" : "Yellow",
            "count" : 6
        },
        {
            "_id" : "Blue",
            "count" : 6
        },
        {
            "_id" : "Green",
            "count" : 6
        }
    ],
    "ok" : 1
}

Whoa, Nellie!

Aggregation can be confusing when you first run into it, which usually happens when trying to figure out how to do grouping. That's why I've shown $group above, but it's is actually one of the trickier aggregation stages.

I suggest slowing down and learning how aggregation works at a high level to allow the concepts to sink in. Once you get that, the specifics are easily demystified by referring to the docs.

How Does Aggregation Work?

Simply put, aggregation takes a collection of documents and turns them into a new collection of documents. Each of these transformations is referred to as an aggregation stage. How each aggregation stage works varies, but you can figure them out easily once you understand what you're looking at.

Aggregation works by passing a collection through a series of stages, each changing the collection and passing it off to the next stage. This is the aggregation pipeline.

An Example

db.people.aggregate([
    // Change the documents in people by sorting.
    { $sort: { name: -1 } },

    // Change the sorted documents from the previous stage
    // by limiting them to the first 3.
    { $limit: 3 },

    // Change which fields are present in the
    // 3 sorted documents.
    { $project: { _id: 0, name: 1, color: 1 } }
]);

{
    "result" : [
        {
            "name" : "Thomas",
            "color" : "Violet"
        },
        {
            "name" : "Thomas",
            "color" : "Green"
        },
        {
            "name" : "Thomas",
            "color" : "Yellow"
        }
    ],
    "ok" : 1
}

What is the Result?

This makes sense, but what is this new object structure? It's not a collection like we saw in the CRUD post. According to TFM, it is a cursor.

Remember, in mongo, the shell interface, cursors are printed out automatically when you don't assign the operation to a variable.

So, it says it's a cursor, and it has the documents in result, but there's definitely something different. Let's compare the keys in the two different cursor types.

var findCursor = db.people.find();
var aggregateCursor = db.people.aggregate([{ $limit: 10 }]);

Object.keys(findCursor).sort();

[
    "_batchSize",
    "_collection",
    "_cursor",
    "_db",
    "_fields",
    "_limit",
    "_mongo",
    "_ns",
    "_numReturned",
    "_options",
    "_query",
    "_skip",
    "_special"
]

Object.keys(aggregateCursor).sort();

[ "ok", "result" ]

Now, those don't appear to be the same, but cursors warrant their own post.

Tips for Working Through Aggregation

In my first draft, I got right into explaining each of the rabbit holes in the aggregation stages. That's dumb. Instead, I would like to teach you to fish.

Plan it Out

Figure out what it is you want to do, like this.

db.people.aggregate([
    // group by color

    // sort by count of color

    // pick the top 2

    // remove all the fields except the color
]);

Pick the Stages

There are only 10 aggregation stages. Read the descriptions to see which one(s) you want to you use.

db.people.aggregate([
    // group by color
    { $group: {} },

    // sort by count of color
    { $sort: {} },

    // pick the top 2
    { $limit: {} },

    // remove all the fields except the color
    { $project: {} }
]);

Set the Stages Up

Now that you know what stages you'll use, go through them one-by-one and read the docs. With our cheatsheet, you can keep your eyes on the prize. It's less likely to you'll be overwhelmed or distracted by all the new concepts and terminology that come along with aggregation.

Right at the top of each stage's docs, it shows the structure we need to use. Additionally, they include lots of detail and several examples. Just fight the temptation to get distracted.

If you can, you should end up with something like this.

db.people.aggregate([
    // group by color
    {
        $group: {
            _id: '$color',
            count: {
                $sum: 1
            }
        }
    },

    // sort by count of color
    { $sort: { count: -1 } },

    // pick the top 2
    { $limit: 2 },

    // remove all the fields except the color
    {
        $project: {
            _id: 0,
            color: '$_id'
        }
    }
]);

{
    "result" : [
        {
            "color" : "Orange"
        },
        {
            "color" : "Red"
        }
    ],
    "ok" : 1
}

References

Friday, September 26, 2014

MongoDB CRUD: Part II

Welcome to part two of our excellent adventure. Before we resume, let's seed our database with some more documents to play with.

var max = 33;
while (max--) {
    db.stomach.insert({
        name: 'Oreo',
        part: max % 3 === 0 ? 'white' : 'black'
    });
}

Update

Now that we've got a bunch of Oreos in our stomach collection, I don't think the Sweetwater we added in Part I was such a good idea. Let's change it.

  1. Find the documents we want to update. We'll do that with another query.
  2. Specify the new values. That's done with the update parameter.
db.stomach.update(
    // query
    { beer: 'Sweetwater' },

    // update parameter
    {
        $set: {
            beer: 'root'
        }
    }
);

We changed the document's beer value from 'Sweetwater' to 'root'. This did not affect the other fields in the document, as you can see.

db.stomach.find({
    beer: {
        $exists: true
    }
}).pretty();

{
    "_id" : ObjectId("5424a5ba28f262081e997a7f"),
    "beer" : "root",
    "count" : 2
}

What if we want to completely replace the fields in the document, though? Who wants rootbeer with cookies? Let's make it more complimentary to the other documents in the collection.

db.stomach.update(
    // query
    {
        beer: {
            $exists: true
        }
    },

    // update parameter
    {
        drink: 'milk',
        type: 'skim',
        ounces: 8
    }
);

By specifying a plain object as the update parameter instead of update operators, we've completely changed the document.

> db.stomach.find({ drink: 'milk' }).pretty();
{
    "_id" : ObjectId("5424a5ba28f262081e997a7f"),
    "drink" : "milk",
    "type" : "skim",
    "ounces" : 8
}

Note that the _id does not change when using update, even when replacing the document.

Now, let's change all those old-fashioned Oreos to one of the new flavors.

db.stomach.update(
    // query
    {
        name: 'Oreo',
        part: 'white'
    },

    // update parameter
    {
        $set: {
            part: 'berry'
        }
    }
);

Let's see the results.

> db.stomach.find({ name: 'Oreo', part: 'berry' }).pretty();
{
    "_id" : ObjectId("542603ba21330a1f47f4bd68"),
    "name" : "Oreo",
    "part" : "berry"
}

Only one of the documents was updated. This brings us to the 3rd parameter of update, the options. The options are described in the documentation, one of which is multi.

db.stomach.update(
    // query
    {
        name: 'Oreo',
        part: 'white'
    },

    // update parameter
    {
        $set: {
            part: 'berry'
        }
    },

    // options
    {
        multi: true
    }
);
> db.stomach.find({ name: 'Oreo', part: 'berry' }).count();
11

By default, update only modifies a single document. With multi, we can make it update all the documents matched by our query.

Delete

Now we've got a stomach collection full of berry cookies and milk, the anchovy pizza isn't really sitting well. Let's barf it up.

db.stomach.remove({
    anchovies: {
        $exists: true
    } 
});

That should seem pretty intuitive by now.

References


Check out the accompanying Github repo to these posts about MongoDB/Mongoose.

Thursday, September 25, 2014

MongoDB CRUD: Part I

In a previous post, I wrote the "M" in MEAN really stands for Mongoose as it is the de facto way to work with MongoDB. However, it's interesting to peek beneath the surface. You don't have to be able to rebuild a manifold in order to drive, but you should probably know how to check the oil.

So, now that we know how to connect, let's figure out how to CRUD documents.

Create

Databases contain collections. Collections contain documents. To insert a new document into a collection, we use the insert method.

db.stomach.insert({
    crust: 'thin',
    pepperoni: true,
    olives: true,
    anchovies: 6
});

Easy, right? That inserted a pizza-like document into the stomach collection. Let's try it again.

db.stomach.insert({
    beer: 'Sweetwater',
    count: 2
});

Pretty straightforward. Also, this demonstrates why NoSQL databases are so cool; documents within a collection don't have to have to the same fields. Collections are like tables, but friendlier. Do you remember going into admin mode and creating the stomach table before we ran these queries?

Exactly.

Read

Now that's we've got a collection and a couple documents, let's see about reading them. What was our collection's name again?

> show collections
stomach
system.indexes

Oh, yeah, stomach. Let's see what's in there.

> db.stomach.find().pretty();
{
    "_id" : ObjectId("5424a39628f262081e997a7e"),
    "crust" : "thin",
    "pepperoni" : true,
    "olives" : true,
    "anchovies" : 6
}
{
    "_id" : ObjectId("5424a5ba28f262081e997a7f"),
    "beer" : "Sweetwater",
    "count" : 2
}

As you can see, MongoDB inserted a unique _id for us because we didn't include one.

find can take a couple arguments to make it more useful.

  • an object of query operators - This specifies what documents you're looking for.
  • an object of projection operators - This specifies what fields you want returned in the results.
db.stomach.find(
    {
        anchovies: {
            $exists: true
        }
    },

    {
        crust: 1,
        pepperoni: 1,
        donkey: 1
    }
);

I'm not going to go over all the options as the documentation is very good, but you get the idea. A couple notes, though.

  • It's worth noting that find returns a cursor that you can iterate through to see the returned documents. Because we didn't assign the expression to a variable, mongo printed out the first 20 results automatically.
  • _id is returned unless you explicitly exclude it in the projection. You can't mix includes and excludes in your projection with the exception of the _id field.
  • We included donkey in the projection, but it doesn't exist. Therefore, it was not in the result.

Summary

My wife just turned on Bill a Ted and it's surprisingly distracting. That means, Update and Delete will come in part II of this post. Party on, dudes!

References


Check out the accompanying Github repo to these posts about MongoDB/Mongoose.

Wednesday, September 24, 2014

Connecting to MongoDB

Connecting to MongoDB with Mongoose is really easy.

mongoose.connect('mongodb://localhost/my-cool-database');

Suppose you want to poke around your database outside of Mongoose, though. How do you connect? Use mongo, the shell interface.

Basic Connection

This connects to the "test" database on "localhost".

mongo

Another Machine/Database

You can specify a different machine and/or database by specifying the "database address."

# connect to another database (on localhost)
mongo some-other-db

# connect to another machine/database
mongo whatever.com/some-db

Warning

You can connect to a machine without specifying a database, but this is NOT how to do it.

mongo localhost

This will connect you to "localhost" using the database "localhost." If you want to connect without automatically selecting a database, use the --nodb option.

mongo localhost --nodb

From there, you can connect to a database manually.

Change Port

By default, MongoDB runs on port 10027. If you're running on a different port, just include that in the "database address."

mongo whatever.com:12345/some-db

Close Connection

Now you can connect to your MongoDB in a few different ways. Since we haven't covered anything else yet, the final thing you need to learn is how to disconnect. Baby steps, right?

You can close the connection with exit or ctrl + c.


Check out the accompanying Github repo to these posts about MongoDB/Mongoose.