Skip to content

3. Working with MongoDB: Basic shell operations

3.1. Collections and Documents

As we already know, the basic unit of information that MongoDB works with is the document, which would be the equivalent of a record in a relational model. These are JSON documents, made up of key-value pairs, and which represent the information in a fairly intuitive way. MongoDB servers, for their part, will store this data in BSON (Binary JSON) format, a binary serialization format.

Regarding JSON documents for MongoDB, some aspects must be taken into account:

  • Regarding the keys:
  • They cannot be null.
  • They can consist of any UTF-8 character, except the characters . or $.
  • Are case-sensitive.
  • They must be unique within the same document.
  • Regarding your values:
  • They can be of any type allowed.
  • Regarding the document:
  • It must have an _id field, with a unique value, which will act as a document identifier.
  • If we do not specify this key, MongoDB will generate it automatically, with an object of type ObjectId.

If the documents are the equivalent of the records, the collections are the equivalent of the tables, with the difference that the collections have a dynamic schema, with which documents of the same collection can present different keys or data types between them.

Collection names will be subject to the following restrictions:

  • They cannot be the empty string (""), nor the null character, nor contain the symbol $.
  • We can use the period (.) in collection names to add prefixes to it, but collections cannot be created with the prefix by system., since this is used for collections internal to the system. For example db.system.test would not be valid, but db.systema.test would.

3.2. Basic operations with MongoDB

In the following, we are going to see some of the basic operations that we can perform on MongoDB:

  • insertOne(document) → Add a document to the collection:
  • db.collection.insertOne({ a:1 })
  • insertMany(documents) → Add a set of document to the collection:
  • db.collection.insertOne([{ a:1 },{ a:2 },{ a:3,b:5 }])
  • find(criteria) → Gets all the documents in a collection that match the specified pattern.
  • db.collection.find({a:1}). Note that the patter will be a json object too.
  • findOne(Criterion) Gets an element of the collection matching the pattern
  • db.collection.findOne()
  • updateOne(Criterion, Operation, [options]) and updateMany(Criterion, Operation, [options]) → Updates one (or several in the case of updateMany) documents in the collection. It requires two parameters:
  • the search criteria of the document to update and
  • the update operation.
  • Supports an optional third parameter for options.
    • db.collection.updateOne({a:1}, {$set: {a:2}})
  • deleteOne(Criterion) and deleteMany(Criterion) → Deletes the documents from a collection that match the criteria
  • db.collection.deleteOne({a:1})

In the following sections we will delve into the different operations.

3.3. Data types

The data types that MongoDB works with are similar to those that we can find in JavaScript and Java. MongoDB supports the basic types described in the following table:

  • null → Represents both the null value and a field that does not exist
  • boolean → Allows the values true and false
  • number → Represent floating point numeric values. If we want to use integer or long integer types, we must use our own classes: NumberInt (32 bits) or NumberLong (64 bits)
  • String → Represents any valid UTF-8 text string.
  • Date → Represents dates, expressed in milliseconds.
  • array → Lists of values that are represented as vectors
  • Embedded documents → Documents can have other documents embedded in them
  • ObjectId → This is the default type for _id fields, and is designed to easily generate globally unique values.

3.3.1. Date

Mongo uses the JavaScript Date type. When we generate a new object of type Date, we must use the New operator, since otherwise we would obtain a representation of the date in the form of a string. For example, if we define the variables a and b as follows:

JavaScript
1
2
3
test> let a=Date()

test> let b=new Date()

We can see that the results are quite different:

JavaScript
test> a
Sun May 08 2022 06:46:01 GMT+0200 (Central European Summer Time)
test> typeof(a)
string

test>b
ISODate("2022-05-08T04:46:09.371Z")

test> typeof(b)
object

3.3.2. Arrays

Arrays can be used to represent either ordered collections, such as lists or queues, or unordered collections, such as sets. As in JavaScript, and unlike other languages, such as Java, each element of the vector can have a different data type, including other objects of vector types.

Let's see some examples about vectors in Javascript and therefore in MongoDB:

JavaScript
// array creation
test> let v={objetos: ["casa", 10, {texto: "hola"}, false] }

// ask for objects components
test> v
{ objetos: [ 'casa', 10, { texto: 'hola' }, false ] }

test> v.objetos
[ 'casa', 10, { texto: 'hola' }, false ]

// ask for array cells
test> v.objetos[1]
10 

test> v.objetos[2]
{ texto: 'hola' }

// change values
test> v.objetos[3]=!v.objetos[3]
true 

test> v
{ objetos: [ 'casa', 10, { texto: 'hola' }, true ] }

3.3.3. Embedded documents

A key-value pair in one document can have another document as value. This is known as embedded documents, and would be when using a JSON object inside another. For example:

JavaScript
> let peli={ 
        titulo: "Rogue One. A Star Wars Story.",
        anyo: 2016,
      director: {
          nombre: "Gareth",
          apellidos:  "Edwards",
          anyo_nacimiento: 1975,
          nacionalidad: "británica"
      }
   }

As we can see, the document itself contains information about the film and its director. In a relational model, we would normally have two tables related to each other. In this case, it is possible that if we want to keep specific information about the directors, we end up with redundant information.

3.3.4. About OBjectIds

The ObjectId class uses 12 bytes, organized as follows:

![ObjectID](./img/ObjectId.png){width=55%}
  • Timestamp (bytes 0-3) → The timestamp in seconds since January 1, 1970.
  • Machine ID (bytes 4-6)→ Unique identifier of the machine, usually a hash of its hostname,
  • PID (bytes 7-8) → Identifier of the process that generates the ObjectID, to guarantee uniqueness within the same machine,
  • Increment (bytes 9-11)→ Auto-incremental value, to guarantee uniqueness in the same second, machine and process.

As we can see, it is a more robust mechanism than an auto-incremental field like in MySQL. This corresponds to the distributed nature of MongoDB, so that objects can be generated in a multi-hosted environment.

3.4. Adding information to collections

Natural way to add elements to the database is through the different insert methods, available in all collections.

3.4.1. insertOne()

Allow to insert one document in the collection. For instance to insert las peli object created in last section we can do:

JavaScript
1
2
3
4
5
test> db.misPelis.insertOne(peli)
{
  acknowledged: true,
  insertedId: ObjectId("6277510ab54867b80b742ddf")
}

As we can see, response is a JSON document that contains a boolean value indicating whether the operation was successful, and an ObjectID, with the automatically assigned ID.

Important

Take into account:

  • If the collection to which we add a document does not exists, it is created automatically.
  • Regarding the _id field, as we can see, it was generated automatically. However, we can indicate this identifier, without it being an ObjectId type, the only restriction is that it be unique, to avoid duplicates.
  • We have not used any schema for the collection, since each document that we insert can have a different schema.

3.4.2. insertMany()

Allow adding several documents into a collection. Then we need to provide a document array:

JavaScript
// we create three objects
test> let peli2={titulo: "Star Wars. A new Hope", anyo: 1977};
test> let peli3={titulo: "Empire Strikes Back", anyo: 1981};
test> let peli4={titulo: "Return of the Jedi", anyo: 1984};

// insert them, in array way
test> db.misPelis.insertMany([peli2, peli3, peli4])
{
  acknowledged: true,
  insertedIds: {
'0': ObjectId("627759a5b54867b80b742de0"),
'1': ObjectId("627759a5b54867b80b742de1"),
'2': ObjectId("627759a5b54867b80b742de2")
  }
}

Important

If an error appears during the insertion, neither document that provokes error neither next documents will be inserted into the collection.

3.5. Removing information

To delete documents from a collection we will use the deleteOne(), deleteMany() or findOneAndDelete() commands, providing them as a parameter a JSON with a condition that we want the document or documents to be deleted to meet.

  • The deleteOne order will only delete the first element that matches the criteria, so if we want to delete a specific document, we must use criteria that correspond to unique identifiers, such as the _id.
  • The deleteMany command will delete all documents matching the criteria.

Both deleteOne and deleteMany return a document with a boolean, indicating whether the operation has been performed, as well as the number of deleted elements (deletedCount).

For its part, findOneAndDelete also deletes a document, based on selection and ordering criteria, but also returning the document that has been deleted.

For example, we create a collection with several elements:

JavaScript
db.pruebas.insertMany([{x:1}, {x:2}, {x:3}, {x:4}, {x:5}, {x:6}, {x:7}]);

// to delete one document
test> db.pruebas.deleteOne({})
{ acknowledged: true, deletedCount: 1 }

// as no confition is set, is satisfied by all documents, 
// then first document will be deleted

// to delete several documents, for instance its x's value greather than 3
test> db.pruebas.deleteMany({x:{$gt:3}})
{ acknowledged: true, deletedCount: 4 }

// delete and return a document
test> db.pruebas.findOneAndDelete({x:2})
{ _id: ObjectId("6277687fb54867b80b742deb"), x: 2 }

If we want to delete all document's collection, we could use drop order instead, but be very carefull, due to it will remove some metainformation.

JavaScript
test> db.pruebas.drop() 

3.6. Documents update

To update documents, we can either opt for replacement updates, using the replaceOne() method, or make modifications to existing documents, using the updateOne(), updateMany() and findOneAndUpdate() methods. These methods will receive two arguments: the first will be the criteria or condition that the documents to be updated must meet, and the second will be a document with either the new document or the updates to be applied.

3.6.1. Replace Update (replace)

The replacement operation, as its name indicates, replaces an entire document that meets the update criteria with another new document. For example, we create a new calendar collection, to store contacts, with information about telephones:

JavaScript
1
2
3
4
5
6
test> db.agenda.insertOne({nombre:"Jose", telefonos:[{trabajo:"55512345", casa:"555111222"}]}
)
{
  acknowledged: true,
  insertedId: ObjectId("627783dbb54867b80b742df8")
}

As we can see, this method returns the _id of the object, through which we will be able to unequivocally identify this document. So, we could replace this document with another by:

JavaScript
test> db.agenda.replaceOne({"_id":ObjectId("62778439b54867b80b742df9")},
  {nombre: "Jose", 
  correos:[{trabajo: "jose@empresa.com"}, 
  {personal: "jose@proveedor.com"}]} )

// and the response is
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0 
}

As we can see, it is about replacing the entire document, with which we can even modify its structure. updates.

As we have anticipated, the modifications are made using the updateOne(), updateMany() and findOneAndUpdate() methods. Similar to the delete operations, the updateOne() method will modify only the first document that matches the given criteria and the updateMany() method, all those that match the criteria. For its part, the findOneAndUpdate() method modifies the document and returns the original document by default, although this is configurable through options.

3.6.1.1. Modifiers

Modifiers are special keys that allow us to specify more complex update operations. Normally, we will not need to replace the entire document, as in the previous case, but rather add or modify specific fields:

  • $set → Assigns a value to a field in the document. If it doesn't exist, it will create it.
  • db.collection.updateOne({criteria}, {$set: {field:value} });
  • $unset → Removes a field from one or more documents. Since we need to introduce a key-value pair, we'll add a boolean as a value.
  • db.collection.updateMany({criteria}, {$unset: {field:true} });
  • $inc → Increments or decrements the numeric value of a key (does not refer to the identifier), creating a new one if it does not exist.
  • db.collection.updateOne({criteria}, {$inc: {field:increment} });
  • $push → Adds elements to an array. If the array does not exist, it creates it, with the elements that we indicate in the push, whereas if it already exists, it adds them to the end of it.
  • db.collection.update({criteria}, {$push: {array_name:{list_of_values} } });
  • $pull → Remove elements from an array based on some criteria.
  • db.collection.update({criteria},{$pull:{vector:element}}).
  • $pop → Removes elements from an array treated as a stack or queue, ie removing the first (-1) or last (1) element.
  • db.collection.update({criteria},{$pop:{vector: [ -1 | 1 ] }})

Information

This modifiers work with both updateOne or updateMany.

3.6.1.2. Upserts

When no document matching the criteria for an update is found, as expected, no change occurs to the collection. On the other hand, sometimes, we may wish that if a document with certain criteria does not exist when we want to modify it, it is created. This is achieved through special updates, called upserts. With this operation, we save ourselves from searching the collection first, to know if we have to perform an insert (if it doesn't exist) or modify (if it does) operation.

To perform an upsert, we will use the third argument of updates, which consists of a document with different options in key-value format, adding the upsert key to true.

JavaScript
db.collection.updateOne({criteria},{modification}, {upsert:true});