Skip to content

Instantly share code, notes, and snippets.

@percyvega
Last active April 21, 2023 20:04
Show Gist options
  • Save percyvega/f434326c9e6a52b45e46d09a43fa6b23 to your computer and use it in GitHub Desktop.
Save percyvega/f434326c9e6a52b45e46d09a43fa6b23 to your computer and use it in GitHub Desktop.

MongoDB

Terms

Database -> Collections -> Documents -> Fields

Concepts

Sharding (Data Partition, Horizontal Partition):
    Breaks up the data among multiple servers.
    Each shard is held on a separate database server instance, to spread the load.
    The meta-information of where the data lives (in which Shard) is kept in a Config Server.

Replication (Replica Set):
    Maintain a copy of all data in multiple servers.
    Replicates your data on any server in a set.
    When you connect, your queries are automatically delegated to one of the members in a set.
    Writes are run on the Primary server/instance, and reads will usually run on any of the Secondary servers/instances.

To download MongoDB image

docker image pull mongo
docker image pull mongo:4.4.19

To look at MongoDB version and exposed ports

docker image inspect mongo

To run the container

docker container run -d --name mongo-on-docker -p 27017:27017 mongo
    Let Docker manage the storage of your database data by writing the database files to disk on the host system using its own internal volume management. This is the default and is easy and fairly transparent to the user.

docker container run -it --name mongo-on-docker -p 27017:27017 -v /Users/percyvega/WORK/MongoDB/learning-mongodb/_data/db:/data/db mongo
    Create a data directory on the host system (outside the container) and mount this to a directory visible from inside the container. This places the database files in a known location on the host system.

docker container run -it --name mongo-on-docker -p 27017:27017 -v /Users/percyvega/WORK/MongoDB/learning-mongodb:/etc/mongo mongo --config /etc/mongo/mongod.conf
    Using a custom MongoDB configuration file.

docker container run -d --name mongo-on-docker -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=example mongo
    Creates a new user and set that user's password. This user is created in the admin authentication database and given the role of root, which is a "superuser" role.

With a docker-compose.yml file:
    version: '3.1'
    services:
        mongo:
            image: mongo
            container_name: mongo-on-docker
            volumes:
                - ./_data/db:/data/db
            ports:
                - 27017:27017

To run a command inside the running container

docker container exec -it mongo-on-docker bash
    To run mongo commands
        mongodb
    To exit
        exit

To look at the logs inside the container

docker container logs mongo-on-docker

Create your connection string with this format:

mongodb://root:password@localhost:27017/?authSource=admin

Mongo Commands using MQL (MongoDB Query Language)

db db.getName() show current database show dbs list all available databases use specific_database to use a specific database db.createCollection("recipes") creates a collection in which to store documents db.users.drop() deletes the users collection

Insert

db.recipes.insertOne({"title": "Ceviche", cook_time: NumberInt(23)})
    If collection doesn't exist, it will be created.
    If no _id field is specified, MongoDB driver will generate it.
    The _id field must be unique.
db.recipes.insertMany([{...}, {...}, {...}, ...])

count

db.recipes.find({}).count()

Find (returns a cursor)

db.recipes.find({}) // or db.getCollection("recipes").find({})
db.recipes.find({}).pretty()
db.recipes.find({title:"Ceviche"})
db.recipes.find({cook_time: 23})
db.recipes.find({_id: ObjectId("6174df12fb7fbb6cd75d6e29")})

findOne (returns a natural document, not a cursor)

db.recipes.findOne()
db.recipes.findOne({_id: ObjectId("5e6fd805fa98021236426a24")})

Non-logical operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $regex

These two are equivalent:
    db.recipes.find({cook_time: 23})
    db.recipes.find({cook_time: {$eq: 23}})
db.recipes.find({cook_time: {$neq: 23}})
db.recipes.find({cook_time: {$gle: 100, $gte: 23}})
db.recipes.find({writtenOn: {$gt: ISODate("2020-02-20T10:15:00Z")}})
db.recipes.find({cook_time: {$gte: 23}, desc: {$regex: /Peru/i}})
    >= 32 and desc contains Peru (case-insensitive)
db.recipes.find({version: {$in: ["v1", "v2"]}})
db.recipes.find({name: {$in: [/^The/, /elevate/]}})
    Any document with a name that starts with "The" or that contains "elevate"
    $in & $nin can be used with regular expressions
db.recipes.find({country: {$regex: /[A-Z]{2,3}/}})

By specifying fields to display (_id will be displayed unless hidden explicitly)

db.recipes.find({}, {"title": 1}).pretty()
    list only the title column
db.recipes.find({"title": "Tacos"}, {"title": 0}).pretty()
    list all except the title column

$or

db.recipes.find({$or: [{cook_time: {$gte: 23}}, {desc: {$regex: /Peru/i}}]})
db.recipes.find({$or: [{cook_time: {$gte: 15}}, {cook_time: {$lte: 50}}]})

$and

These two are equivalent:
    db.recipes.find({$and: [{cook_time: {$gte: 23}}, {desc: {$regex: /ican/i}}]})
    db.recipes.find({{cook_time: {$gte: 23}}, {desc: {$regex: /ican/i}})
    // if you used the same field with the previous syntax, then the result would be incoherent
These two are equivalent:
    db.recipes.find({$and: [{cook_time: {$gte: 15}}, {cook_time: {$lte: 50}}]})
    db.recipes.find({cook_time: {$gte: 15, $lte: 50}})

Empty, null and $exists

db.recipes.find({cook_time: ""})
    cook_time is an empty space
db.recipes.find({cook_time: null})
    cook_time is empty or does not exist (i.e. is not present)
db.recipes.find({cook_time: {$exists: false}})
    cook_time does not exist (i.e. is not present)

$type

db.people.find({e_mail: {$type: "string"}})
db.people.find({e_mail: {$type: "null"}})
    e_mail is null (not missing)
db.people.find({address: {$type: "object"}})
db.people.find({Favorite_Colors: {$type: "array"}})

sort

db.recipes.find({}).sort({"title": 1})
    title ascending
db.recipes.find({}).sort({"title": -1})
    title descending

limit

db.recipes.find().limit(3)

skip

db.recipes.find({}).skip(1)

Free Text Search

To support fast text searches on string and arrays of string fields.
    It will only work for the fields that are "text" indexed and that are $type: "string"
        db.people.createIndex({name: "text", skills: "text"})
db.people.find({$text: {$search: "engineer Percy"}})
    text-indexed fields contain any of the words: engineer, Percy
db.people.find({$text: {$search: "Green Red"}})
    contain either of these words
db.people.find({$text: {$search: "Green Red"}}, {score: {$meta: "textScore"}}).sort({"score": 1})
    sort them by match score desc

Find in Arrays

db.recipes.find({tags: "mexican"})
    all documents with a tags array containing "mexican"
db.recipes.find({tags: {$in: ["easy", "quick"]}})
    one of the tags contains easy, quick or both
These two are equivalent:
    db.recipes.find({tags: {$all: ["easy", "quick"]}})
    db.recipes.find({$and: [{tags: "easy"}, {tags: "quick"}]})
        tags contains at least easy and quick, in any order
db.recipes.find({tags: {$size: 1}})
    tags contains exactly 1 element
These two are equivalent:
    db.recipes.find({"tags.1": {$exists: true}})
    db.recipes.find({"tags": {$exists: true, $not: {$size: 0}}}
        tags contains at least 1 element
These two are equivalent:
    db.recipes.find({emergency_contacts: {$elemMatch: {relationship: "Brother", age: {$gte: 18}}}})
    db.recipes.find({"emergency_contacts.relationship": "Brother", "emergency_contacts.age": {$gte: 18}})
        emergency_contacts contains at least one object with relationship=Brother and age>=18

Find by Exact Array

db.recipes.find({tags: ["sweets", "easy"]})
    tags arrays that are exactly this, and in the exact order

Find in objects

db.people.find({"address.country": "United States", "address.state": "Connecticut"})
    The address object has these fields and values

Find by Exact Object

db.people.find({"address": {state: "Connecticut", country: "United States"}})
    documents with exactly this (same list of keys, same values, and same order)

Update fields

It's an atomic operation for a single document
_id fields cannot be modified
$set updates an existing field or creates it with the specified value if it doesn't exist

$set - updates of adds fields

db.recipes.updateOne({title: "Pizza"}, {$set: {title: "Yummy Pizza"}})
db.recipes.updateMany({title: "Pizza"}, {$set: {title: "Yummy Pizza"}})

db.recipes.updateOne({title: "Pizza"}, {$set: {title: "Yummy Pizza", spicy: true}})
db.recipes.updateOne({title: "Pizza"}, {$set: {jalapenos: true}})
    sets (or adds, if it doesn't exist) the document field jalapenos

$unset - remove field

db.recipes.updateOne({title: "Pizza"}, {$unset: {jalapenos: 1}})
    unsets (removes) the field jalapenos from the document

$inc - Increment

db.recipes.updateOne({title: "Pizza"}, {$inc: {cook_time: 5}})
    increment cook_time by 5. The number can also be negative.

$mul - Multiply

db.recipes.updateOne({title: "Pizza"}, {$mul: {cost: 1.5}})
    multiplies the field by 1.5, and if it doesn't exist, sets it to 0

Replace document

replaceOne

db.recipes.replaceOne({_id: "123", {name: "Percy", age: "23"}})
    replaces the entire document with the new document, leaving only the key intact.

Upsert

Works with updateOne, updateMany, and replaceOne

db.getCollection('recipes').updateOne({title: "Ceviches"}, {$set: {prep_time: 8}}, {upsert: true})
    if title=Ceviches exist, its prep_time will be set to 8.
    if title=Ceviches doesn't exist, a document with _id, title, and prep_time will be inserted.

db.getCollection('recipes').updateOne({title: "Ceviches"}, {$set: {prep_time: 8}, $setOnInsert: {likes_count: 0, desc: "Yet another Peruvian recipe"}}, {upsert: true})
    if title=Ceviches doesn't exist, a document with those extra setOnInsert fields will be inserted.

Update arrays

$push - add elements

db.recipes.updateOne({title: "Pizza"}, {$push: {likes: 23}})
    will push (add) to the "likes" array the element 23 at the end of the array
db.recipes.updateOne({title: "Pizza"}, {$push: {likes: {$each: [8, 23, 79]}}})
    will push (add) to the "likes" array these elements at the end of the array
db.recipes.updateOne({title: "Pizza"}, {$push: {likes: {$each: [8, 23, 79], $position: 0}}})
    will push (add) to the "likes" array these elements at the specified index
    keep in mind that the $position modifier can only be used with the $each modifier

$pop - remove element

db.recipes.updateOne({_id: "mongo101"}, {$pop: {likes: -1}})
    removes from the "likes" array the first element
db.recipes.updateOne({_id: "mongo101"}, {$pop: {likes: 1}})
    removes from the "likes" array the last element

$pull - remove elements

db.recipes.updateOne({title: "Pizza"}, {$pull: {ratings: {$lte: 5}})}
    will pull (remove) from the "ratings" array all values <= 5

Delete documents

db.recipes.deleteOne({title: "Delete me"})
db.recipes.deleteMany({title: "Delete me"})

Bulk Writes

db.recipes.bulkWrite([
    {insertOne: {document: {title: "MongoDB Conf"}}},
    {updateOne: {filter: {_id: 1}, update: {title: {$set: "MongoDB Conference"}}}},
    {deleteOne: {filter: {_id: 10}}}
], {ordered: false})

Indexes

db.recipes.getIndexes()
db.recipes.find({cook_time: 23}).explain("executionStats") // "totalKeysExamined" : 0.0, "totalDocsExamined" : 9.0
db.recipes.createIndex({"cook_time": 1})
db.recipes.find({cook_time: 23}).explain("executionStats") // "totalKeysExamined" : 2.0, "totalDocsExamined" : 2.0
    This shows that the index made the execution search only 2 documents intead of 9.

db.user.createIndex({name: 1}, {age: -1})
    creates an ascending-order index for name, and a descending-order index for age

db.user.createIndex({email: 1}, {unique: true})
    creates an ascending-order index for field email, making it also unique
db.user.getIndexes()
db.user.dropIndex("name.given_1")
    specify the actual name of the index, not the field indexed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment