Saturday, September 2, 2017

MongoDB: Introduction and CRUD Operations

MongoDB makes an excellent IoT datastore, and we will be covering appropriate concepts in detail. In this tutorial we will:

Introduction

MongoDB is an example of a NoSQL database. This not only means that the data cannot be queried by SQL, but that the data is not stored in tables with primary key-foreign key relationships. The data need not be normalized. There are no joins when you query MongoDB. Instead, you must perform the joins in code, or denormalize the data and accept all the problems that come with denormalization.

Data is stored in JSON documents which are placed in a collection. These documents are schemaless: documents don't need to have the same fields, can be of different sizes, etc. This makes adding additional fields trivial (no ALTER TABLE command needed). This also allows business management to be clueless squishy undecided flexible. Yea, that's it, flexible. It allows business management to be flexible.

The differences in terminology are summarized in the following table:

Relational DB MongoDB
Database Database
Table Collection
Record (or Row) JSON Document
Field (or Column) Field
Primary Key Primary Key (defaults to _id)
Table Join Embedded Documents

 

Sample Database

Without the ability to join two or more tables, designing a MongoDB data model seems alien to long-time relational database architects. Compensation for this comes from the fact that the data doesn't have to be normalized, so the same data can be replicated across multiple records documents.

We must instead design our documents so that what would ordinarily be stored in distinct tables is instead stored in one document. In other words, we must denormalize the data.

Let's create a database called "qeftsg" that will hold info about the old TV series "Queer Eye for the Straight Guy". In this show, five fabulous gay guys dispensed their fabulous advice to straight guys, thus making the world a better, more fabulous place. Inside that database will be a collection called "cast" that will hold information on the fabulous five stars of the show: Ted, Carson, Kyan, Thom, and Jai. We will record their names, ages, contact info, and what they contributed to the show. This info can be modeled in JSON as the following five documents:

{
 name: "Ted Allen",
 description: "Food and Wine Connoisseur",
 specialties: ["Alcohol", "Beverages", "Food Preparation", "Food Presentation"],
 age: 52,
 contactInfo: 
 {
  website: "tedallen.net",
  email: "info@tedallen.net"
 }
}

{
 name: "Carson Kressley",
 description: "Fashion Savant",
 specialties: ["Clothing", "Fashion", "Personal Styling"],
 age: 47,
 contactInfo: 
 {
  website: "carsonkressley.com",
  facebook: "carsonkressley"
 }
}

{
 name: "Kyan Douglas",
 description: "Grooming Guru",
 specialties: ["Hair", "Grooming", "Personal Hygiene", "Makeup"],
 age: 47,
 contactInfo: 
 {
  instagram: "kyandouglas",
  facebook: "kyandouglasactual"
 }
}

{
 name: "Thom Filicia",
 description: "Design Doctor",
 specialties: ["Interior Design", "Home Organization"],
 age: 48,
 contactInfo: 
 {
  facebook: "thomfiliciainc",
  email: "info@thomfilicia.com",
  website: "thomfilicia.com"
 }
}

{
 name: "Jai Rodriguez",
 description: "Culture Vulture",
 specialties: ["Popular Culture", "Relationships", "Social Interaction"],
 age: 38,
 contactInfo: 
 {
  website: "myspace.com/jairodriguezmusic",
  facebook: "JaiRodriguezfanpage"
 }
}

The fields in MongoDB are allowed to be one of the following datatypes, so these are indeed valid MongoDB documents:

  • String
  • Integer
  • Double
  • Boolean
  • Array
  • Date
  • Object
  • Null
  • Timestamp is for INTERNAL use only!
  • Plus several others

 

MongoDB Shell

MongoDB can be downloaded from mongodb.com. Once unzipped, the folder name will be something like mongodb-osx-x86_64-enterprise-3.4.7. I have renamed it to be "mongodb" and moved it directly under the hard drive. To start the MongoDB daemon, open a Terminal window and enter the following commands:

cd mongodb
./bin/mongod

The most direct way of interacting with MongoDB is a command line tool called mongo. After the MongoDB is running, open a separate Terminal window and enter:

mongo
to get to the shell:

The MongoDB Shell provides a JavaScript REPL (read-evaluate-print-loop) environment. It is possible to write whole programs in the shell, though we will not explore that option here.

Now that we're at the shell, let's try some commands:

db

returns the current database, which by default is called test.

show dbs

lists the available databases.

 

CRUD Operations

Let us see how to perform Create-Retrieve-Update-Delete operations in MongoDB.

 

Create

To create the database, use this command:

use qeftsg

Even though the database is named "qeftsg", we still refer to it by "db". If a database with that name already exists, MongoDB switches to that db for subsequent commands. If we use the command

show dbs
then "qeftsg" is NOT listed. It will appear only once data has been added. So let's get to it!

To create a collection and add data to it, we use either

db.<collection-name>.insertOne(<JSON-object>);
or
db.<collection-name>.insertMany(<Array-of-JSON-objects>);

We could insert the records documents one by one, like this:

db.cast.insertOne(
{
 name: "Ted Allen",
 description: "Food and Wine Connoisseur",
 specialties: ["Alcohol", "Beverages", "Food Preparation", "Food Presentation"],
 age: 52,
 contactInfo: 
 {
  website: "tedallen.net",
  email: "info@tedallen.net"
 }
}
);

Better still, we can wrap those five docs into an array and use insertMany:

db.cast.insertMany(
[
{
 name: "Ted Allen",
 description: "Food and Wine Connoisseur",
 specialties: ["Alcohol", "Beverages", "Food Preparation", "Food Presentation"],
 age: 52,
 contactInfo: 
 {
  website: "tedallen.net",
  email: "info@tedallen.net"
 }
},
{
 name: "Carson Kressley",
 description: "Fashion Savant",
 specialties: ["Clothing", "Fashion", "Personal Styling"],
 age: 47,
 contactInfo: 
 {
  website: "carsonkressley.com",
  facebook: "carsonkressley"
 }
},
{
 name: "Kyan Douglas",
 description: "Grooming Guru",
 specialties: ["Hair", "Grooming", "Personal Hygiene", "Makeup"],
 age: 47,
 contactInfo: 
 {
  instagram: "kyandouglas",
  facebook: "kyandouglasactual"
 }
},
{
 name: "Thom Filicia",
 description: "Design Doctor",
 specialties: ["Interior Design", "Home Organization"],
 age: 48,
 contactInfo: 
 {
  facebook: "thomfiliciainc",
  email: "info@thomfilicia.com",
  website: "thomfilicia.com"
 }
},
{
 name: "Jai Rodriguez",
 description: "Culture Vulture",
 specialties: ["Popular Culture", "Relationships", "Social Interaction"],
 age: 38,
 contactInfo: 
 {
  website: "myspace.com/jairodriguezmusic",
  facebook: "JaiRodriguezfanpage"
 }
}
]
);

MongoDB lets us know that those documents have been created:

{
 "acknowledged" : true,
 "insertedIds" : [
  ObjectId("59a9e665893968118ac1d85a"),
  ObjectId("59a9e665893968118ac1d85b"),
  ObjectId("59a9e665893968118ac1d85c"),
  ObjectId("59a9e665893968118ac1d85d"),
  ObjectId("59a9e665893968118ac1d85e")
 ]
}

Notice that the argument of insertOne and insertMany are JSON, but quotes around the field names are not required.

Now that our database has data in it, it will be listed when we enter

show dbs

Response:

admin   0.000GB
local   0.000GB
qeftsg  0.000GB

To see the collections in the current database we enter

show collections
and the "cast" collection will be listed. Great!

 

Retrieve

How to retrieve data? That's where the find command comes in:

db.<collection-name>.find(<query>, <projection>);

The two arguments are JSON objects. The first specifies what we're looking for, the second (which is optional) specifies which fields we want to get back. For example:

db.cast.find({});
will return all fields in all the documents. It also returns an autogenerated _id field.

We can choose which fields by using projections as the second argument of the find method. For example,

db.cast.find({}, {name: 1});

will return the name plus the _id field:

{ "_id" : ObjectId("59a9e665893968118ac1d85a"), "name" : "Ted Allen" }
{ "_id" : ObjectId("59a9e665893968118ac1d85b"), "name" : "Carson Kressley" }
{ "_id" : ObjectId("59a9e665893968118ac1d85c"), "name" : "Kyan Douglas" }
{ "_id" : ObjectId("59a9e665893968118ac1d85d"), "name" : "Thom Filicia" }
{ "_id" : ObjectId("59a9e665893968118ac1d85e"), "name" : "Jai Rodriguez" } 

Suppose we wanted just the name and not _id?

db.cast.find({}, {name: 1, _id: 0});
and now only the name is returned:
{ "name" : "Ted Allen" }
{ "name" : "Carson Kressley" }
{ "name" : "Kyan Douglas" }
{ "name" : "Thom Filicia" }
{ "name" : "Jai Rodriguez" } 

Suppose we only wanted to find their names and descriptions. This will do that:

db.cast.find({}, {_id: 0, name: 1, description: 1});
The result is:
{ "name" : "Ted Allen", "description" : "Food and Wine Connoisseur" }
{ "name" : "Carson Kressley", "description" : "Fashion Savant" }
{ "name" : "Kyan Douglas", "description" : "Grooming Guru" }
{ "name" : "Thom Filicia", "description" : "Design Doctor" }
{ "name" : "Jai Rodriguez", "description" : "Culture Vulture" }

Now that we know how to use the projection, let us focus on the query. Exact matches can be done like this:

db.cast.find({age: 47}, {_id: 0, name: 1});

This returns the name of all cast members who are 47 years old, and so returns Carson and Kyan as two JSON documents:

{ "name" : "Carson Kressley" }
{ "name" : "Kyan Douglas" }

Here's an example of a comparison:

db.cast.find({age: {$lt: 44}}, {_id: 0, name: 1});
This will return all docs for people who are less than 44 years of age, so it returns Jai:
{ "name" : "Jai Rodriguez" }

Here's a list of comparison operators:

$eq Equals
$gt Greater Than
$gte Greater Than or Equal To
$lt Less Than
$lte Less Than or Equal To
$ne Not Equals
$in Is Value in Array
$nin Is Value NOT in Array

Here's an example of using $in:

db.cast.find({age: {$in: [47 , 48]}}, {_id: 0, name: 1, age: 1});
This returns people who are exactly 47 or 48 years old, so it returns Carson, Kyan, and Thom:
{ "name" : "Carson Kressley", "age" : 47 }
{ "name" : "Kyan Douglas", "age" : 47 }
{ "name" : "Thom Filicia", "age" : 48 }

Suppose we wanted to get cast members who are 47 years old and are culture vultures:

db.cast.find({age:47, description:"Culture Vulture"}, {_id: 0, name: 1, description: 1});

This query returns no documents since by default, conditions inside the query JSON are interpreted as being "anded" together, and no cast member meets both criteria.

In order to get all members who are 47 years old or are culture vultures, we use the $or operator:

db.cast.find({$or:[{age:47}, {description:"Culture Vulture"}]}, {_id: 0, name: 1, description: 1});

This returns just what we expected:

{ "name" : "Carson Kressley", "description" : "Fashion Savant" }
{ "name" : "Kyan Douglas", "description" : "Grooming Guru" }
{ "name" : "Jai Rodriguez", "description" : "Culture Vulture" }

Let's get the Fab Fives' names and Facebook info, if any:

db.cast.find({}, {_id: 0, name: 1, "contactInfo.facebook": 1});

Notice the quotes around contactInfo.facebook. This is needed since, by itself, contactInfo.facebook is not a valid JSON field name. Here are the results:

{ "name" : "Ted Allen", "contactInfo" : {  } }
{ "name" : "Carson Kressley", "contactInfo" : { "facebook" : "carsonkressley" } }
{ "name" : "Kyan Douglas", "contactInfo" : { "facebook" : "kyandouglasactual" } }
{ "name" : "Thom Filicia", "contactInfo" : { "facebook" : "thomfiliciainc" } }
{ "name" : "Jai Rodriguez", "contactInfo" : { "facebook" : "JaiRodriguezfanpage" } }

Suppose we only want a few of the matching documents? That's what limit is for...

db.cast.find({age: {$lte: 48}}, {name: 1, _id: 0, age: 1}).limit(2);
and we get two documents back:
{ "name" : "Carson Kressley" }
{ "name" : "Kyan Douglas" }

Sorting is done like this:

db.cast.find({}, {name: 1, _id: 0}).sort({name: 1});
returns
{ "name" : "Carson Kressley" }
{ "name" : "Jai Rodriguez" }
{ "name" : "Kyan Douglas" }
{ "name" : "Ted Allen" }
{ "name" : "Thom Filicia" }
and
db.cast.find({}, {name: 1, _id: 0}).sort({name: -1});
returns
{ "name" : "Thom Filicia" }
{ "name" : "Ted Allen" }
{ "name" : "Kyan Douglas" }
{ "name" : "Jai Rodriguez" }
{ "name" : "Carson Kressley" }

See the MongoDB documentation for more information about querying data.

 

Update

Suppose that Ted had a birthday. We update his age using the following command:

db.cast.update({name: "Ted"}, {$set: {age: 53}});

Documents are updated using an "upsert" process - if no matching document is found, MongoDB will create one. The response indicates the number of documents matched, the number upserted, and the number modified.

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 });

The same command can also be used to add new fields to the documents:

db.cast.updateMany({}, {$set: {withShow: true}});
MongoDB responds with
{ "acknowledged" : true, "matchedCount" : 5, "modifiedCount" : 5 }
and if we then do
db.cast.find({}, {_id: 0, name: 1, withShow: 1});
we see that a new field has indeed been added to all the docs:
{ "name" : "Ted Allen", "withShow" : true }
{ "name" : "Carson Kressley", "withShow" : true }
{ "name" : "Kyan Douglas", "withShow" : true }
{ "name" : "Thom Filicia", "withShow" : true }
{ "name" : "Jai Rodriguez", "withShow" : true }

 

Delete

To remove documents, we use the remove command:

db.<collection-name>.remove(<criteria>);

For example, Jai quit QEFTSG, and so we can remove him like this:

db.cast.remove({name: "Jai"});
MongoDB responds with
WriteResult({ "nRemoved" : 0 })

Zero records were removed! What went wrong? We need to specify the exact name of the person to be deleted:

db.cast.remove({name: "Jai Rodriguez"});
MongoDB responds with
WriteResult({ "nRemoved" : 1 })
and if we then do:
db.cast.find({}, {name: 1, _id: 0});
we see that only four cast members remain:
{ "name" : "Ted Allen" }
{ "name" : "Carson Kressley" }
{ "name" : "Kyan Douglas" }
{ "name" : "Thom Filicia" }

To delete the entire collection, do this:

db.cast.drop();

Now since the qeftsg had only one collection in it, and we just dropped it, so qeftsg now has no data in it. Thus, qeftsg will not be listed when we use

show dbs

 

Conclusion

Finally, to exit the shell,

quit()
and to halt the MongoDB daemon, press CTRL-C.

There is much more to MongoDB queries than just shown, especially for retrieve, and nothing can beat the official MongoDB tutorials.

Older versions of MongoDB had a simple web interface for examining databases, performing queries. That feature has been removed. If a GUI is desired, there are several out there, including a simple desktop application called Robo 3T (formerly Robomongo). A more advanced tool is called Studio 3T, which is free for non-commercial applications.

No comments:

Post a Comment