Introduction to NoSQL Databases: Exploring MongoDB & Relational Databases

by | MongoDB

Introduction to NoSQL Databases

What Are NoSQL Databases?

NoSQL databases, or “Not Only SQL” databases, are a class of database management systems that provide a mechanism for storage and retrieval of data that is modeled differently from the tabular relations used in relational databases (RDBMS).

Key Characteristics of NoSQL Databases:

  1. Schema-less: NoSQL databases are typically schema-less, meaning that data can be stored in structures as JSON, XML, or BSON without a fixed schema.
  2. Scalability: NoSQL databases are designed to scale horizontally, meaning new servers can be added to share the load.
  3. Flexible Data Models: NoSQL databases support various data models like document, key-value, wide-column, and graph.

Comparison with RDBMS:

Relational Databases (RDBMS):

  • Schema: Fixed schema.
  • Scalability: Scales vertically (usually) by adding resources to a single node.
  • ACID Transactions: Strong consistency.

NoSQL Databases:

  • Schema: Flexible schema.
  • Scalability: Scales horizontally by distributing data across multiple nodes.
  • BASE Transactions: Eventual consistency, Availability, and Partition tolerance.

Focus on MongoDB:

MongoDB is a popular NoSQL database that stores data in flexible, JSON-like documents. Below are stepwise instructions to set up and start using MongoDB.

Installation Instructions:

Installing MongoDB on Windows:

  1. Download MongoDB:

  2. Install MongoDB:

    • Follow the installation wizard to install MongoDB. Make sure to choose the complete installation option.
  3. Set Up MongoDB:

    • Create the datadb directory. This is the default location where MongoDB stores data. You can create this directory using the command prompt:
      md datadb

  4. Start MongoDB:

    • Run mongod to start the MongoDB server. Use the command prompt:
      "C:Program FilesMongoDBServer<version>binmongod.exe"

  5. Verify Installation:

    • In a new command prompt, connect to MongoDB using mongo.exe:
      "C:Program FilesMongoDBServer<version>binmongo.exe"

Installing MongoDB on macOS:

  1. Install Brew (If not already installed):

    • Open terminal and install Homebrew (skip if already installed):
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

  2. Install MongoDB:

    • Use Brew to install MongoDB:
      brew tap mongodb/brew
      brew install mongodb-community@5.0

  3. Start MongoDB:

    • Start the MongoDB server using the command:
      brew services start mongodb/brew/mongodb-community

  4. Verify Installation:

    • Open another terminal window and run the MongoDB shell:
      mongo

Working with MongoDB:

Once MongoDB is installed and running, you can start working with databases, collections, and documents.

Basic MongoDB Commands:


  • Create or Switch Database:


    use myDatabase  # Switches to myDatabase, creates it if it doesn't exist.


  • Create Collection:


    db.createCollection("myCollection")


  • Insert Document:


    db.myCollection.insertOne({ name: "John", age: 30 })


  • Find Document:


    db.myCollection.find({ name: "John" })


  • Update Document:


    db.myCollection.updateOne({ name: "John" }, { $set: { age: 31 } })


  • Delete Document:


    db.myCollection.deleteOne({ name: "John" })

Conclusion

With this setup, you now have a basic understanding and a working MongoDB environment. From this point, you can explore further into MongoDB capabilities such as indexing, aggregation, replication, and sharding. This guide provides the foundation needed to dive deeper into NoSQL databases and specifically MongoDB.

Understanding MongoDB

What is MongoDB?

MongoDB is a NoSQL database that provides a flexible, scalable way to store and retrieve large amounts of unstructured or semi-structured data. Unlike traditional relational databases, MongoDB doesn’t use tables and rows. Instead, it uses collections and documents.

Core Concepts

Collections and Documents

  • Collections: Analogous to tables in relational databases. A collection stores documents having similar or different structures.
  • Documents: Analogous to rows in relational databases. A document in MongoDB is a record in JSON/BSON format.

Basic Operations

Inserting Documents

Here’s a pseudocode example of inserting a document into a collection:

// Connect to MongoDB
connection = connectToMongoDB("mongodb://localhost:27017")

// Select Database
db = connection.getDatabase("myDatabase")

// Select Collection
collection = db.getCollection("myCollection")

// Create a Document
document = {
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com"
}

// Insert the Document into Collection
collection.insertOne(document)

Querying Documents

Here’s how to query documents from a collection:

// Find Single Document
query = { "name": "Alice" }
result = collection.findOne(query)
print(result)

// Find Multiple Documents
query = { "age": { "$gt": 25 } }
results = collection.find(query)
for document in results:
    print(document)

Updating Documents

Updating documents in MongoDB can be done using either updateOne or updateMany.

// Update a Single Document
filter = { "name": "Alice" }
update = { "$set": { "email": "newemail@example.com" } }
collection.updateOne(filter, update)

// Update Multiple Documents
filter = { "age": { "$lt": 25 } }
update = { "$inc": { "age": 1 } }
collection.updateMany(filter, update)

Deleting Documents

Here’s how to delete documents from a collection:

// Delete a Single Document
filter = { "name": "Alice" }
collection.deleteOne(filter)

// Delete Multiple Documents
filter = { "age": { "$gt": 30 } }
collection.deleteMany(filter)

Indexing

Indexes in MongoDB improve the efficiency of search operations. Here’s how to create an index:

// Create Index on the "name" Field
collection.createIndex({ "name": 1 })

// Create a Compound Index
collection.createIndex({ "name": 1, "age": -1 })

Aggregation

Aggregation operations process data records and return computed results. It’s similar to SQL’s GROUP BY.

pipeline = [
    { "$match": { "age": { "$gte": 25 } } },
    { "$group": { "_id": "$age", "count": { "$sum": 1 } } }
]

results = collection.aggregate(pipeline)
for result in results:
    print(result)

Comparison with Relational Databases

  • Schema Design: MongoDB offers more flexibility as documents can have varying structures. In contrast, relational databases enforce a strict schema.
  • Scalability: MongoDB is designed to scale horizontally across many servers, while relational databases typically scale vertically.
  • Transactions: While MongoDB supports multi-document transactions, they are more naturally suited for transactional operations in a relational database.

Conclusion

MongoDB’s flexible schema and scalability make it ideal for applications dealing with varied or unstructured data, while traditional relational databases are suitable for applications requiring strict schema adherence and complex transactions. Understanding these concepts allows one to effectively decide which database solution best fits their project’s requirements.

Data Modeling in MongoDB

Introduction

Data modeling in MongoDB involves designing how data is stored and retrieved. Unlike traditional relational databases, MongoDB uses a flexible, schema-less design which allows data to be stored in JSON-like documents.

Document Structure

Documents in MongoDB are similar to rows in relational databases, but much more flexible, as they support nested fields and varied data types. Fields in a document can vary from document to document within the same collection.

Example Document

{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "John Doe",
  "age": 29,
  "email": "johndoe@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Anytown",
    "state": "CA",
    "zipcode": "90210"
  },
  "orders": [
    {
      "order_id": "A123",
      "product": "Laptop",
      "quantity": 1,
      "price": 900
    },
    {
      "order_id": "B456",
      "product": "Mouse",
      "quantity": 2,
      "price": 20
    }
  ]
}

Schema Design Techniques

Embedding Documents

Embedding is a common modeling technique where nested documents are used within a document. This design is useful when the dataset has a strong relationship and is read together often.

Example

An e-commerce order with details can embed product information directly within the order document.

{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "user_id": ObjectId("507f1f77bcf86cd799439011"),
  "date": "2023-10-06",
  "items": [
    { "product": "Laptop", "quantity": 1, "price": 900 },
    { "product": "Mouse", "quantity": 2, "price": 20 }
  ],
  "status": "shipped"
}

Referencing Documents

Referencing is used when embedding is not suitable, such as when the relationships are weak or when the embedded documents would grow indefinitely.

Example

User profiles and their orders can be stored in separate collections and linked using references.

User Document

{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "John Doe",
  "age": 29,
  "email": "johndoe@example.com"
}

Order Document

{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "user_id": ObjectId("507f1f77bcf86cd799439011"),
  "date": "2023-10-06",
  "items": [
    { "product": "Laptop", "quantity": 1, "price": 900 },
    { "product": "Mouse", "quantity": 2, "price": 20 }
  ],
  "status": "shipped"
}

In practice, querying the orders for a user would involve a join-like operation using the user_id.

Handling Relationships

One-to-One

This can be handled by embedding or referencing depending on the size and access pattern.

Example with Embedding

{
  "_id": ObjectId("507f1f77bcf86cd799439013"),
  "username": "janedoe",
  "password": "securepassword",
  "profile": {
    "name": "Jane Doe",
    "email": "janedoe@example.com"
  }
}

One-to-Many

One-to-Many relationships can be modeled via embedding or referencing.

Example with Embedding (comments within a blog post)

{
  "_id": ObjectId("507f1f77bcf86cd799439014"),
  "title": "A Sample Blog Post",
  "body": "This is the content of the blog post.",
  "comments": [
    { 
      "user": "user1", 
      "text": "Great post!", 
      "date": "2023-10-01"
    },
    { 
      "user": "user2", 
      "text": "Thanks for sharing!", 
      "date": "2023-10-02"
    }
  ]
}

Example with Referencing (large datasets)

{
  "_id": ObjectId("507f1f77bcf86cd799439015"),
  "title": "A Sample Blog Post",
  "body": "This is the content of the blog post.",
  "comments": [
    ObjectId("507f1f77bcf86cd799439016"),
    ObjectId("507f1f77bcf86cd799439017")
  ]
}

Referenced Comment Document

{
  "_id": ObjectId("507f1f77bcf86cd799439016"),
  "post_id": ObjectId("507f1f77bcf86cd799439015"),
  "user": "user1",
  "text": "Great post!",
  "date": "2023-10-01"
}

Many-to-Many

Many-to-Many relationships often use referencing through intermediate collections.

Example – Users and Groups
User Document

{
  "_id": ObjectId("507f1f77bcf86cd799439018"),
  "username": "johnsmith",
  "groups": [
    ObjectId("507f1f77bcf86cd799439019"),
    ObjectId("507f1f77bcf86cd799439020")
  ]
}

Group Document

{
  "_id": ObjectId("507f1f77bcf86cd799439019"),
  "name": "Admins",
  "members": [
    ObjectId("507f1f77bcf86cd799439018"),
    ObjectId("507f1f77bcf86cd799439021")
  ]
}

Conclusion

Data modeling in MongoDB is highly flexible, allowing for both embedded documents and referencing strategies. This flexibility must be applied thoughtfully depending on use cases, data size, and query patterns. Utilize embedding for closely linked data with a one-to-one or one-to-few relationship, and use referencing for large datasets or many-to-many relationships. Implementing these strategies effectively will ensure scalable and performant data models in MongoDB.

Querying and Aggregation Framework in MongoDB

Introduction

In MongoDB, querying is a core aspect that allows you to retrieve data stored in collections efficiently. Aggregation, on the other hand, provides advanced data processing capabilities, allowing for complex data transformations and computations within the database. In this guide, we’ll walk through practical examples of querying and using the aggregation framework in MongoDB.

Querying

Basic Querying

To retrieve documents from a collection, use the find method. Here are some basic examples:

// Retrieve all documents in the collection
db.collection.find({})

// Find documents that match a specific condition
db.collection.find({ "key": "value" })

// Using comparison operators
db.collection.find({ "age": { "$gt": 25 } })

// Combining conditions with AND and OR
db.collection.find({ "$and": [{ "age": { "$gt": 25 } }, { "status": "A" }] })
db.collection.find({ "$or": [{ "age": { "$gt": 25 } }, { "status": "A" }] })

Projections

Projections specify or restrict fields to return in the result set:

// Retrieve only specific fields
db.collection.find({ "status": "A" }, { "name": 1, "age": 1, "_id": 0 })

Aggregation Framework

Using the Aggregation Pipeline

The aggregation framework in MongoDB uses a pipeline approach to process data. Here’s a concise breakdown of using the aggregation pipeline:

// Sample pipeline with $match, $group, and $sort stages
db.collection.aggregate([
  { 
    "$match": { "status": "A" } 
  },
  { 
    "$group": { 
      "_id": "$age", 
      "total": { "$sum": 1 } 
    } 
  },
  { 
    "$sort": { "total": -1 } 
  }
])

Common Stages

$match

Filters documents to pass only those that match the specified condition(s):

{
  "$match": { "status": "A" }
}

$group

Groups input documents by the specified _id expression and accumulates values for each group:

{
  "$group": { 
    "_id": "$field", 
    "total": { "$sum": 1 } 
  }
}

$project

Passes along documents with only the specified fields:

{
  "$project": { 
    "name": 1, 
    "age": 1, 
    "_id": 0 
  }
}

$sort

Sorts all input documents and returns them in order:

{
  "$sort": { "total": -1 }
}

$lookup

Performs a left outer join to another collection to filter in documents from the “joined” collection for processing:

{
  "$lookup": {
    "from": "otherCollection",
    "localField": "localKey",
    "foreignField": "foreignKey",
    "as": "joinedDocs"
  }
}

Putting It All Together

Here’s a comprehensive example of querying and using the aggregation framework to derive meaningful insights from data:

// Finding all documents where the status is "A" and projecting only name and age
db.collection.find({ "status": "A" }, { "name": 1, "age": 1, "_id": 0 })

// Aggregation pipeline example
db.collection.aggregate([
  {
    "$match": { "status": "A" }
  },
  {
    "$project": { 
      "name": 1, 
      "age": 1, 
      "department": 1
    }
  },
  {
    "$group": { 
      "_id": "$department", 
      "averageAge": { "$avg": "$age" }, 
      "totalEmployees": { "$sum": 1 }
    }
  },
  {
    "$sort": { "averageAge": -1 }
  }
])

This should cover the essential aspects of querying and using the aggregation framework in MongoDB.

Replication in MongoDB

MongoDB uses a replica set to provide replication, which is a group of mongod instances that maintain the same data set. Replica sets provide redundancy and high availability, and are the basis for all production deployments.

Setting Up a Replica Set


  1. Start MongoDB Instances: Start several mongod instances. Each instance serves as a member of the replica set.


    mongod --replSet myReplSet --port 27017 --dbpath /path/to/db1
    mongod --replSet myReplSet --port 27018 --dbpath /path/to/db2
    mongod --replSet myReplSet --port 27019 --dbpath /path/to/db3


  2. Initiate the Replica Set: Connect to one of the MongoDB instances and initiate the replica set.


    rs.initiate(
    {
    _id: "myReplSet",
    members: [
    { _id: 0, host: "localhost:27017" },
    { _id: 1, host: "localhost:27018" },
    { _id: 2, host: "localhost:27019" }
    ]
    }
    )


  3. Check the Replica Set Status: Verify the status of the replica set.


    rs.status()

Sharding in MongoDB

Sharding is the process of storing data records across multiple machines and it is MongoDB’s approach to meeting the demands of data growth.

Setting Up Sharding


  1. Start Config Server Replica Set:


    mongod --configsvr --replSet configReplSet --port 26050 --dbpath /path/to/config1
    mongod --configsvr --replSet configReplSet --port 26051 --dbpath /path/to/config2
    mongod --configsvr --replSet configReplSet --port 26052 --dbpath /path/to/config3


  2. Initiate Config Server Replica Set:


    rs.initiate(
    {
    _id: "configReplSet",
    configsvr: true,
    members: [
    { _id: 0, host: "localhost:26050" },
    { _id: 1, host: "localhost:26051" },
    { _id: 2, host: "localhost:26052" }
    ]
    }
    )


  3. Start Shard Servers:


    mongod --shardsvr --replSet shard1 --port 27017 --dbpath /path/to/shard1
    mongod --shardsvr --replSet shard2 --port 27018 --dbpath /path/to/shard2
    mongod --shardsvr --replSet shard3 --port 27019 --dbpath /path/to/shard3


  4. Initiate Shard Replica Sets:


    rs.initiate(
    {
    _id: "shard1",
    members: [
    { _id: 0, host: "localhost:27017" }
    ]
    }
    )
    rs.initiate(
    {
    _id: "shard2",
    members: [
    { _id: 0, host: "localhost:27018" }
    ]
    }
    )
    rs.initiate(
    {
    _id: "shard3",
    members: [
    { _id: 0, host: "localhost:27019" }
    ]
    }
    )


  5. Start MongoS:


    mongos --configdb configReplSet/localhost:26050,localhost:26051,localhost:26052 --port 27020


  6. Add Shards to the Cluster:


    sh.addShard("shard1/localhost:27017")
    sh.addShard("shard2/localhost:27018")
    sh.addShard("shard3/localhost:27019")


  7. Enable Sharding for a Database:


    sh.enableSharding("myDatabase")


  8. Shard a Collection:


    sh.shardCollection("myDatabase.myCollection", { shardKeyField: 1 })

The above steps offer a thorough explanation and practical implementation of setting up replication and sharding in MongoDB using general knowledge and pseudocode applicable in real life scenarios.

Comparative Analysis: NoSQL vs Relational Databases

Data Model

Relational Databases

  • Schema: Fixed; requires definition before adding data.
  • Tables: Organize data into structured tables with rows and columns.
  • Relationships: Defined through primary and foreign keys.

Example: User and Order Tables

CREATE TABLE Users (
    UserID INT PRIMARY KEY,
    Name VARCHAR(100),
    Email VARCHAR(100)
);
 
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    UserID INT,
    Product VARCHAR(100),
    Amount DECIMAL(10, 2),
    FOREIGN KEY (UserID) REFERENCES Users(UserID)
);

MongoDB

  • Schema: Dynamic; no predefined schema needed.
  • Collections: Equivalent to tables but without fixed structure.
  • Documents: Store data in BSON format (JSON-like).

Example: User and Order Collections

db.users.insertMany([
    { _id: ObjectId("507f191e810c19729de860ea"), name: "John Doe", email: "john@example.com" },
    { _id: ObjectId("507f191e810c19729de860eb"), name: "Jane Doe", email: "jane@example.com" }
]);

db.orders.insertMany([
    { _id: ObjectId("507f191e810c19729de860ec"), user_id: ObjectId("507f191e810c19729de860ea"), product: "Laptop", amount: 1200.00 },
    { _id: ObjectId("507f191e810c19729de860ed"), user_id: ObjectId("507f191e810c19729de860ea"), product: "Phone", amount: 800.00 }
]);

Query Language

Relational Databases

SQL Query for User Orders

SELECT Users.Name, Orders.Product, Orders.Amount
FROM Users
JOIN Orders ON Users.UserID = Orders.UserID
WHERE Users.UserID = 1;

MongoDB

MongoDB Query for User Orders

db.orders.aggregate([
    {
        $lookup: {
            from: "users",
            localField: "user_id",
            foreignField: "_id",
            as: "user_info"
        }
    },
    {
        $match: { "user_info._id": ObjectId("507f191e810c19729de860ea") }
    },
    {
        $project: {
            product: 1,
            amount: 1,
            "user_info.name": 1
        }
    }
]);

Scalability

Relational Databases

  • Vertical Scaling: Add more power (CPU, RAM) to an existing server.
  • Challenges: Limitations in scaling, becomes expensive.

MongoDB

  • Horizontal Scaling: Add more servers to handle increased load.
  • Sharding: Distributes data across multiple servers.

MongoDB Sharding Example

sh.enableSharding("mydatabase")
sh.shardCollection("mydatabase.orders", { "_id": "hashed" })

Transactions

Relational Databases

  • ACID Compliance: Ensures atomicity, consistency, isolation, and durability.

SQL Transaction Example

START TRANSACTION;
INSERT INTO Users (UserID, Name, Email) VALUES (2, 'Jane Doe', 'jane@example.com');
INSERT INTO Orders (OrderID, UserID, Product, Amount) VALUES (1, 2, 'Tablet', 500.00);
COMMIT;

MongoDB

  • Atomic Operations: Originally limited to single document.
  • MongoDB 4.0+: Multi-document transactions.

MongoDB Transaction Example

const session = db.getMongo().startSession();
session.startTransaction();
try {
    db.users.insertOne({ _id: ObjectId("507f191e810c19729de860eb"), name: "Jane Doe", email: "jane@example.com" }, { session });
    db.orders.insertOne({ _id: ObjectId("507f191e810c19729de860ed"), user_id: ObjectId("507f191e810c19729de860eb"), product: "Tablet", amount: 500.00 }, { session });
    session.commitTransaction();
} catch (error) {
    session.abortTransaction();
    throw error;
} finally {
    session.endSession();
}

Conclusion

This comparative analysis covers core aspects such as data modeling, query languages, scalability, and transactions between traditional relational databases and MongoDB. These practical examples illustrate the structural differences, query methodologies, and how each type of database handles critical operations, which can be applied directly to real-life scenarios.

Related Posts

Mastering MongoDB Indexing Strategies

MongoDB Schema Design and Data Modeling

A comprehensive guide to mastering MongoDB schema design and data modeling. Explore best practices and strategies for creating efficient schemas tailored to your application’s needs.