Skip to content

MongoDB Queries

This page covers the core CRUD operations and query operators in MongoDB.

The examples on this page use a trips collection containing HSL city bike trip data.


Selecting a Database

Use the use command to switch to a database. The db variable then refers to that database in all subsequent commands.

js
use bikedata

If the database does not exist yet, MongoDB will create it when you first insert data into it.


Inserting Documents

js
// Insert a single document
db.trips.insertOne({
  departure: ISODate("2025-07-31T23:59:58Z"),
  return: ISODate("2025-08-01T00:08:37Z"),
  departureStationName: "Isoisänsilta",
  returnStationName: "Vilhonvuorenkatu",
  coveredDistance: 1002,
  duration: 516
})

// Insert multiple documents
db.trips.insertMany([
  {
    departure: ISODate("2025-07-31T23:59:54Z"),
    return: ISODate("2025-08-01T00:03:42Z"),
    departureStationName: "Karhupuisto",
    returnStationName: "Sörnäinen (M)",
    coveredDistance: 712,
    duration: 224
  },
  {
    departure: ISODate("2025-07-31T23:59:53Z"),
    return: ISODate("2025-08-01T00:05:21Z"),
    departureStationName: "Hietalahdentori",
    returnStationName: "Apollonkatu",
    coveredDistance: 1805,
    duration: 323
  }
])

Documents can also contain arrays and nested objects:

js
db.users.insertOne({
  username: "john.doe",
  email: "john@example.com",
  roles: ["user", "editor"],
  preferences: {
    theme: "dark",
    language: "en"
  }
})

ObjectId

MongoDB automatically generates a unique ObjectId for every document when it is inserted.


Querying (Read)

Find All Documents

js
db.collection.find()

Example — fetch all trips:

js
db.trips.find()

Find with a Filter

js
// Trips departing from Karhupuisto
db.trips.find({ departureStationName: "Karhupuisto" })

Querying Array Fields

When a field contains an array, you can query it directly with a value. MongoDB will match any document where the array contains that value.

js
// Find users who have the "editor" role
db.users.find({ roles: "editor" })

// Find users who have both "editor" and "admin" roles
db.users.find({ roles: { $all: ["editor", "admin"] } })

Querying Nested Documents

When a document contains embedded objects, use dot notation to query fields inside them. The field path must be in quotes.

js
// Users whose preferences are set to dark theme
db.users.find({ "preferences.theme": "dark" })

// Users whose preferences language is "en" and theme is "dark"
db.users.find({
  "preferences.language": "en",
  "preferences.theme": "dark"
})

Dot notation also works with comparison operators:

js
// Products with a rating score greater than 4
db.products.find({ "rating.score": { $gt: 4 } })

Projection — Selecting Specific Fields

By default, find() returns all fields. Pass a second argument to include or exclude fields. Use 1 to include and 0 to exclude.

js
// Return only station names and distance
db.trips.find({}, { departureStationName: 1, returnStationName: 1, coveredDistance: 1, _id: 0 })

// Return everything except the duration field
db.trips.find({}, { duration: 0 })

SQL equivalent:

sql
SELECT departureStationName, returnStationName, coveredDistance FROM trips

WARNING

You cannot mix includes and excludes in the same projection, except for _id which can always be excluded.

Counting Documents

js
// Count all trips
db.trips.countDocuments({})

// Count trips departing from Karhupuisto
db.trips.countDocuments({ departureStationName: "Karhupuisto" })

SQL equivalent:

sql
SELECT COUNT(*) FROM trips WHERE departureStationName = 'Karhupuisto'

Sorting

Use .sort() to order results. Use 1 for ascending and -1 for descending.

js
// Sort by distance, shortest first
db.trips.find().sort({ coveredDistance: 1 })

// Sort by duration, longest first
db.trips.find().sort({ duration: -1 })

// Sort by multiple fields — first by departure station, then by distance
db.trips.find().sort({ departureStationName: 1, coveredDistance: -1 })

SQL equivalent:

sql
SELECT * FROM trips ORDER BY departureStationName ASC, coveredDistance DESC

Comparison Operators

Use comparison operators as the value of a field filter.

OperatorMeaning
$eqEqual to
$neNot equal to
$gtGreater than
$gteGreater than or equal to
$ltLess than
$lteLess than or equal to
$inMatches any value in an array
$ninDoes not match any value in an array

Examples

js
// Trips longer than 1000 meters
db.trips.find({ coveredDistance: { $gt: 1000 } })

// Trips departing from Karhupuisto or Hietalahdentori
db.trips.find({ departureStationName: { $in: ["Karhupuisto", "Hietalahdentori"] } })

Logical Operators

OperatorMeaning
$andReturns documents matching all conditions
$orReturns documents matching any condition
$notReturns documents that do not match the condition
$norReturns documents that match neither condition

Example

js
// Trips from Karhupuisto that are longer than 500 meters
db.trips.find({ $and: [
  { departureStationName: "Karhupuisto" },
  { coveredDistance: { $gt: 500 } }
]})

SQL equivalent:

sql
SELECT * FROM trips WHERE departureStationName = 'Karhupuisto' AND coveredDistance > 500

Existence Check

Use $exists to filter by whether a field is present in the document.

js
// Trips where the 'coveredDistance' field exists
db.trips.find({ coveredDistance: { $exists: true } })

// Trips where the 'coveredDistance' field does NOT exist
db.trips.find({ coveredDistance: { $exists: false } })

Date and Time Queries

MongoDB stores dates as ISODate objects. You can create them in the shell with ISODate() or new Date().

Filtering by Date

Use comparison operators to filter by date, just like with numbers.

js
// Trips departing after July 31, 2025
db.trips.find({ departure: { $gt: ISODate("2025-07-31") } })

// Trips departing before August 2025
db.trips.find({ departure: { $lt: ISODate("2025-08-01") } })

Date Range

Combine $gte and $lte to query a date range.

js
// Trips during July 2025
db.trips.find({
  departure: {
    $gte: ISODate("2025-07-01"),
    $lte: ISODate("2025-07-31T23:59:59Z")
  }
})

SQL equivalent:

sql
SELECT * FROM trips WHERE departure BETWEEN '2025-07-01' AND '2025-07-31 23:59:59'

Sorting by Date

js
// Oldest trips first
db.trips.find().sort({ departure: 1 })

// Newest trips first
db.trips.find().sort({ departure: -1 })

Pagination

.skip(n) and .limit(n)

MethodBehaviour
.skip(n)Skips the first n results
.limit(n)Returns at most n results

Example — page 2 with 10 trips per page:

js
db.trips.find().skip(10).limit(10)

SQL equivalent:

sql
SELECT * FROM trips LIMIT 10, 10

Updating Documents

js
// Rename a station in all matching trips
db.trips.updateMany(
  { departureStationName: "Sörnäinen (M)" },
  { $set: { departureStationName: "Sörnäinen" } }
)

// Update one document by its ID
db.trips.updateOne(
  { _id: ObjectId('63e362b9415552734e896049') },
  { $set: { coveredDistance: 1050 } }
)

Update Operators

OperatorDescription
$setSets the value of a field
$unsetRemoves a field from the document
$incIncrements a numeric field by a given amount
$pushAdds an element to an array field
$pullRemoves matching elements from an array field

Examples

js
// Add 100 meters to a trip's covered distance
db.trips.updateOne(
  { _id: ObjectId('63e362b9415552734e896049') },
  { $inc: { coveredDistance: 100 } }
)

// Remove the 'duration' field from all trips
db.trips.updateMany(
  {},
  { $unset: { duration: "" } }
)

// Add a tag to a document's tags array
db.trips.updateOne(
  { _id: ObjectId('63e362b9415552734e896049') },
  { $push: { tags: "weekend" } }
)

// Remove a tag from a document's tags array
db.trips.updateOne(
  { _id: ObjectId('63e362b9415552734e896049') },
  { $pull: { tags: "weekend" } }
)

Updating Array Elements with the Positional $ Operator

To update a specific element inside an array, use the positional $ operator. The filter must match the array element you want to update, and $ refers to that matched element.

js
// Change the theme preference for a user whose preferences include "dark"
db.users.updateOne(
  { username: "john.doe", "preferences.theme": "dark" },
  { $set: { "preferences.$.theme": "light" } }
)

In this example:

  • The filter matches the user and the array element where preferences.theme is "dark"
  • preferences.$ refers to that matched element
  • $set updates only the theme field of that specific element

Another example — update a specific role's access level in a roles array:

js
db.users.updateOne(
  { username: "john.doe", "roles.name": "editor" },
  { $set: { "roles.$.accessLevel": 2 } }
)

WARNING

The positional $ operator only updates the first matching element in the array.

Arithmetic Operators

These operators can be used in aggregation pipelines and in pipeline-style updates to calculate values from existing fields.

OperatorDescriptionExample
$addAddition{ $add: ["$price", 10] }
$subtractSubtraction{ $subtract: ["$price", "$discount"] }
$multiplyMultiplication{ $multiply: ["$price", "$quantity"] }
$divideDivision{ $divide: ["$coveredDistance", 1000] }
$modModulo (remainder){ $mod: ["$duration", 60] }
$roundRound to n decimals{ $round: ["$avgSpeed", 1] }

Updating with Calculated Values

You can use an aggregation pipeline as the update parameter to calculate new field values from existing fields. Wrap the pipeline stages in an array [] instead of {}.

js
// Add average speed (km/h) to all trips
// Formula: (distance in meters / 1000) / (duration in seconds / 3600)
db.trips.updateMany(
  {},
  [
    { $set: { avgSpeed: { $round: [{ $divide: [
      { $multiply: ["$coveredDistance", 3.6] },
      "$duration"
    ]}, 1] } } }
  ]
)

After this update, each document has a new avgSpeed field:

json
{
  "departureStationName": "Isoisänsilta",
  "returnStationName": "Vilhonvuorenkatu",
  "coveredDistance": 1002,
  "duration": 516,
  "avgSpeed": 7.0
}

Another example — add a cost estimate assuming a price of 0.10 EUR per minute:

js
// Add trip cost (duration in seconds / 60 * price per minute)
db.trips.updateMany(
  {},
  [
    { $set: { cost: { $round: [{ $multiply: [
      { $divide: ["$duration", 60] },
      0.10
    ]}, 2] } } }
  ]
)

TIP

The $round operator rounds the result to a given number of decimal places. $round: [value, 1] rounds to one decimal, $round: [value, 2] rounds to two decimals.


Deleting Documents

js
// Delete all trips
db.trips.deleteMany({})

// Delete trips shorter than 500 meters
db.trips.deleteMany({ coveredDistance: { $lt: 500 } })

// Delete a single trip by ID
db.trips.deleteOne({ _id: ObjectId('63e362b9415552734e896049') })

Aggregation

The aggregation pipeline processes documents through a sequence of stages. Each stage transforms the data and passes the result to the next stage.

js
db.collection.aggregate([ stage1, stage2, ... ])

Common Stages

StageDescription
$matchFilters documents (like find)
$groupGroups documents and calculates aggregate values
$sortSorts the results
$projectReshapes documents — include, exclude, or rename fields
$limitLimits the number of results
$unwindDeconstructs an array field into separate documents

Individual Stage Examples

$match — filter documents before further processing:

js
// Only pass trips longer than 1 km to the next stage
db.trips.aggregate([
  { $match: { coveredDistance: { $gt: 1000 } } }
])

$group — group documents and calculate values:

js
// Total distance per departure station
db.trips.aggregate([
  { $group: { _id: "$departureStationName", totalDistance: { $sum: "$coveredDistance" } } }
])

$sort — order the results:

js
// Sort trips by duration, longest first
db.trips.aggregate([
  { $sort: { duration: -1 } }
])

$project — include, exclude, or rename fields:

js
// Show only station names and distance in km
db.trips.aggregate([
  { $project: { departureStationName: 1, returnStationName: 1, distanceKm: { $divide: ["$coveredDistance", 1000] }, _id: 0 } }
])

$limit — return only the first n documents:

js
// Return only the first 5 trips
db.trips.aggregate([
  { $limit: 5 }
])

$unwind — expand an array field into separate documents:

js
// Each role in the array becomes its own document
db.users.aggregate([
  { $unwind: "$roles" }
])

Step-by-Step Example — Trips per Station

Suppose we have a trips collection with the following documents:

json
[
  { "_id": 1, "departureStationName": "Isoisänsilta",    "returnStationName": "Vilhonvuorenkatu", "coveredDistance": 1002, "duration": 516 },
  { "_id": 2, "departureStationName": "Karhupuisto",     "returnStationName": "Sörnäinen (M)",    "coveredDistance": 712,  "duration": 224 },
  { "_id": 3, "departureStationName": "Hietalahdentori", "returnStationName": "Apollonkatu",      "coveredDistance": 1805, "duration": 323 },
  { "_id": 4, "departureStationName": "Karhupuisto",     "returnStationName": "Isoisänsilta",     "coveredDistance": 950,  "duration": 412 },
  { "_id": 5, "departureStationName": "Isoisänsilta",    "returnStationName": "Karhupuisto",      "coveredDistance": 1100, "duration": 380 }
]

We want to find the average distance per departure station, sorted highest first. The pipeline:

js
db.trips.aggregate([
  { $group: { _id: "$departureStationName", avgDistance: { $avg: "$coveredDistance" }, tripCount: { $sum: 1 } } },
  { $sort: { avgDistance: -1 } },
  { $project: { _id: 0, station: "$_id", avgDistance: 1, tripCount: 1 } }
])

Here is what the data looks like after each stage:

Stage 1 — $group: group by departure station, calculate average distance and trip count.

json
[
  { "_id": "Hietalahdentori", "avgDistance": 1805, "tripCount": 1 },
  { "_id": "Isoisänsilta",    "avgDistance": 1051, "tripCount": 2 },
  { "_id": "Karhupuisto",     "avgDistance": 831,  "tripCount": 2 }
]

Stage 2 — $sort: sort by average distance, highest first.

json
[
  { "_id": "Hietalahdentori", "avgDistance": 1805, "tripCount": 1 },
  { "_id": "Isoisänsilta",    "avgDistance": 1051, "tripCount": 2 },
  { "_id": "Karhupuisto",     "avgDistance": 831,  "tripCount": 2 }
]

Stage 3 — $project: rename _id to station and hide _id.

json
[
  { "avgDistance": 1805, "tripCount": 1, "station": "Hietalahdentori" },
  { "avgDistance": 1051, "tripCount": 2, "station": "Isoisänsilta" },
  { "avgDistance": 831,  "tripCount": 2, "station": "Karhupuisto" }
]

Example — Count Trips per Departure Station

js
db.trips.aggregate([
  { $group: { _id: "$departureStationName", count: { $sum: 1 } } }
])

SQL equivalent:

sql
SELECT departureStationName, COUNT(*) AS count FROM trips GROUP BY departureStationName

Example — Average Duration of Trips from Karhupuisto

js
db.trips.aggregate([
  { $match: { departureStationName: "Karhupuisto" } },
  { $group: { _id: null, avgDuration: { $avg: "$duration" } } }
])

SQL equivalent:

sql
SELECT AVG(duration) AS avgDuration FROM trips WHERE departureStationName = 'Karhupuisto'

Example — $unwind with Array Fields

$unwind deconstructs an array field so that each array element becomes its own document. This is useful when you need to group or count by values inside arrays.

Given documents with a roles array:

json
[
  { "_id": 1, "username": "alice", "roles": ["admin", "editor"] },
  { "_id": 2, "username": "bob",   "roles": ["editor"] },
  { "_id": 3, "username": "carol", "roles": ["admin", "user"] }
]

Count how many users have each role:

js
db.users.aggregate([
  { $unwind: "$roles" },
  { $group: { _id: "$roles", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

After $unwind, the data is expanded — each role gets its own document:

json
[
  { "_id": 1, "username": "alice", "roles": "admin" },
  { "_id": 1, "username": "alice", "roles": "editor" },
  { "_id": 2, "username": "bob",   "roles": "editor" },
  { "_id": 3, "username": "carol", "roles": "admin" },
  { "_id": 3, "username": "carol", "roles": "user" }
]

After $group and $sort, the final result:

json
[
  { "_id": "admin",  "count": 2 },
  { "_id": "editor", "count": 2 },
  { "_id": "user",   "count": 1 }
]

Example — Top 3 Longest Trips

js
db.trips.aggregate([
  { $sort: { coveredDistance: -1 } },
  { $limit: 3 },
  { $project: { departureStationName: 1, returnStationName: 1, coveredDistance: 1, _id: 0 } }
])

Further Reading

Lapland University of Applied Sciences

© 2026 Juha Petäjäjärvi

© 2026 Juha Petäjäjärvi