Last modified: October 01, 2022

This article is written in: 🇺🇸

Querying NoSQL Databases

Querying NoSQL databases requires a different approach compared to relational databases due to their diverse data models and storage mechanisms. This guide focuses on MongoDB, a popular NoSQL database, and explores how to query data effectively using its powerful query language.

After reading the material, you should be able to answer the following questions:

  1. How does MongoDB's document model differ from traditional relational databases, and what advantages does it offer for schema design?
  2. What are the key differences between basic queries (like find, countDocuments, and distinct) and advanced queries (such as aggregation, text search, and geospatial queries) in MongoDB?
  3. How do indexing strategies in MongoDB, including single field, compound, multikey, text, and geospatial indexes, enhance query performance?
  4. What is the purpose of the explain() method in MongoDB, and how can it be used to analyze and optimize query execution plans?
  5. In what scenarios would you choose to use specific types of indexes (e.g., text indexes for search functionality or geospatial indexes for location-based queries) in MongoDB?

Introduction to MongoDB

Data Model

Example Document:

{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "John Doe",
  "age": 30,
  "email": "john.doe@example.com",
  "addresses": [
    {
      "street": "123 Main St",
      "city": "Anytown",
      "zip": "12345"
    },
    {
      "street": "456 Elm St",
      "city": "Othertown",
      "zip": "67890"
    }
  ]
}

Query Language

Examples of Query Operators:

Basic Queries

Find

Example: Retrieve all users aged 25

Sample Data in users Collection:

Suppose we have the following documents in our users collection:

db.users.insertMany([
  { _id: 1, name: "Alice", age: 25, email: "alice@example.com", city: "New York" },
  { _id: 2, name: "Bob", age: 30, email: "bob@example.com", city: "Los Angeles" },
  { _id: 3, name: "Carol", age: 25, email: "carol@example.com", city: "Chicago" },
  { _id: 4, name: "Dave", age: 28, email: "dave@example.com", city: "New York" }
])

Code:

db.users.find({ age: 25 })

Result:

{ "_id" : 1, "name" : "Alice", "age" : 25, "email" : "alice@example.com", "city" : "New York" }
{ "_id" : 3, "name" : "Carol", "age" : 25, "email" : "carol@example.com", "city" : "Chicago" }

Example with Projection: Retrieve users aged 25, but only show their names and emails

Code:

db.users.find(
  { age: 25 },
  { name: 1, email: 1, _id: 0 }
)

Result:

{ "name" : "Alice", "email" : "alice@example.com" }
{ "name" : "Carol", "email" : "carol@example.com" }

Count

Example: Count the number of users aged 25

Code:

db.users.countDocuments({ age: 25 })

Result:

2

Distinct

Example: Get a list of unique cities where users aged 25 live

Code:

db.users.distinct("city", { age: 25 })

Result:

[ "New York", "Chicago" ]

Advanced Queries

Aggregation

Syntax:

db.collection.aggregate(pipeline, options)

Example: Group users by city and count the number of users in each city

Sample Data:

Using the same users collection as before.

Code:

db.users.aggregate([
  { $group: { _id: "$city", count: { $sum: 1 } } }
])

Result:

{ "_id" : "New York", "count" : 2 }
{ "_id" : "Los Angeles", "count" : 1 }
{ "_id" : "Chicago", "count" : 1 }

Example: Calculate the average age of users in each city

Code:

db.users.aggregate([
  { $group: { _id: "$city", averageAge: { $avg: "$age" } } }
])

Result:

{ "_id" : "New York", "averageAge" : 26.5 }
{ "_id" : "Los Angeles", "averageAge" : 30 }
{ "_id" : "Chicago", "averageAge" : 25 }

MongoDB supports text search through text indexes, allowing you to perform search operations on string content.

Sample Data in articles Collection:

db.articles.insertMany([
  { _id: 1, title: "Introduction to MongoDB", content: "MongoDB is a NoSQL database." },
  { _id: 2, title: "NoSQL Databases", content: "NoSQL databases are non-relational." },
  { _id: 3, title: "Relational Databases", content: "SQL databases are relational." },
  { _id: 4, title: "Advantages of NoSQL", content: "NoSQL databases like MongoDB are scalable." }
])

Creating a Text Index:

db.articles.createIndex({ content: "text" })

The syntax for text search is db.collection.find({ $text: { $search: searchString } }).

Example: Find articles that contain the word "NoSQL"

Code:

db.articles.find({ $text: { $search: "NoSQL" } })

Result:

{ "_id" : 1, "title" : "Introduction to MongoDB", "content" : "MongoDB is a NoSQL database." }
{ "_id" : 2, "title" : "NoSQL Databases", "content" : "NoSQL databases are non-relational." }
{ "_id" : 4, "title" : "Advantages of NoSQL", "content" : "NoSQL databases like MongoDB are scalable." }

Advanced Text Search:

For phrase search, enclose the phrase in double quotes.

Example: Find articles containing the phrase "NoSQL databases"

Code:

db.articles.find({ $text: { $search: "\"NoSQL databases\"" } })

Result:

{ "_id" : 2, "title" : "NoSQL Databases", "content" : "NoSQL databases are non-relational." }
{ "_id" : 4, "title" : "Advantages of NoSQL", "content" : "NoSQL databases like MongoDB are scalable." }

To exclude terms, use a minus sign before the word.

Example: Find articles that contain "NoSQL" but not "MongoDB"

Code:

db.articles.find({ $text: { $search: "NoSQL -MongoDB" } })

Result:

{ "_id" : 2, "title" : "NoSQL Databases", "content" : "NoSQL databases are non-relational." }

Geospatial Queries

Storing Location Data:

Sample Data in places Collection:

db.places.insertMany([
  {
    _id: 1,
    name: "Central Park",
    location: {
      type: "Point",
      coordinates: [-73.9667, 40.78]
    }
  },
  {
    _id: 2,
    name: "Times Square",
    location: {
      type: "Point",
      coordinates: [-73.9855, 40.7580]
    }
  },
  {
    _id: 3,
    name: "Empire State Building",
    location: {
      type: "Point",
      coordinates: [-73.9857, 40.7484]
    }
  }
])

Creating a 2dsphere Index:

db.places.createIndex({ location: "2dsphere" })

The syntax for geospatial queries is:

db.collection.find({
  location: {
    $near: {
      $geometry: point,
      $maxDistance: distance
    }
  }
})

Example: Find places within 1,000 meters of Times Square

Code:

db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-73.9855, 40.7580]
      },
      $maxDistance: 1000  // distance in meters
    }
  }
})

Result:

Assuming that the Empire State Building is within 1,000 meters of Times Square:

{ "_id" : 2, "name" : "Times Square", "location" : { "type" : "Point", "coordinates" : [ -73.9855, 40.758 ] } }
{ "_id" : 3, "name" : "Empire State Building", "location" : { "type" : "Point", "coordinates" : [ -73.9857, 40.7484 ] } }

Indexing in MongoDB

Creating Indexes

Example: Create an index on the age field

db.users.createIndex({ age: 1 })

Use 1 for ascending order and -1 for descending order.

Compound Indexes:

Indexes can be created on multiple fields, known as compound indexes.

db.users.createIndex({ age: 1, city: 1 })

Types of Indexes

Index Usage

Use the explain() method to understand how MongoDB executes a query and whether it utilizes an index.

db.users.find({ age: 25 }).explain("executionStats")

Monitoring Indexes:

Use db.collection.getIndexes() to list all indexes on a collection.

db.users.getIndexes()

Dropping Indexes:

Remove unnecessary indexes to optimize performance and reduce storage overhead.

db.users.dropIndex("age_1")

Table of Contents

    Querying NoSQL Databases
    1. Introduction to MongoDB
      1. Data Model
      2. Query Language
    2. Basic Queries
      1. Find
      2. Count
      3. Distinct
    3. Advanced Queries
      1. Aggregation
      2. Text Search
      3. Geospatial Queries
    4. Indexing in MongoDB
      1. Creating Indexes
    5. Types of Indexes
    6. Index Usage