Tutorial

How To Create Queries in MongoDB

Published on September 14, 2021
Default avatar

By Mateusz Papiernik

Software Engineer, CTO @Makimo

How To Create Queries in MongoDB

The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

Introduction

Documents stored in a MongoDB database can vary widely. Some might be relatively small and contain only a few entries, like items in a shopping list. Others might be highly complex, containing dozens of fields of different types, arrays holding multiple values, and even other documents nested within the larger structure.

Regardless of how complex your documents are or how many you have, most often you won’t need to review the data in all of them at once. Instead, you’ll more likely want to only retrieve documents that satisfy one or more particular conditions. Similar to how you would find your holiday destination by selecting a range of filters on a booking website, such as distance from the seaside, pet-friendliness, a pool, and nearby parking, you can precisely query MongoDB to find exactly the documents you need. MongoDB provides a robust query mechanism for defining filtering criteria when retrieving documents.

In this tutorial, you’ll learn how to query MongoDB collections using a different range of filters and conditions. You will also learn what cursors are and how to use them within the MongoDB shell.

Prerequisites

To follow this tutorial, you will need:

Note: The linked tutorials on how to configure your server, install, and then secure MongoDB installation refer to Ubuntu 20.04. This tutorial concentrates on MongoDB itself, not the underlying operating system. It will generally work with any MongoDB installation regardless of the operating system as long as authentication has been enabled.

Step 1 — Preparing the Sample Database

To explain how to create queries in MongoDB — including how to filter documents with multiple fields, nested documents, and arrays — this guide uses an example database containing a collection of documents that describe the five highest mountains in the world.

To create this sample collection, connect to the MongoDB shell as your administrative user. This tutorial follows the conventions of the prerequisite MongoDB security tutorial and assumes the name of this administrative user is AdminSammy and its authentication database is admin. Be sure to change these details in the following command to reflect your own setup, if different:

  1. mongo -u AdminSammy -p --authenticationDatabase admin

When prompted, enter the password you set when you created your administrative user. After providing the password, your prompt will change to a greater-than (>) sign:

Note: On a fresh connection, the MongoDB shell will automatically connect to the test database by default. You can safely use this database to experiment with MongoDB and the MongoDB shell.

Alternatively, you could also switch to another database to run all of the example commands given in this tutorial. To switch to another database, run the use command followed by the name of your database:

  1. use database_name

To understand how MongoDB filters documents with multiple fields, nested documents and arrays, you’ll need sample data complex enough to allow exploring different types of queries. As mentioned previously, this guide uses a sample collection of the five highest mountains in the world.

The documents in this collection will follow this format. This example document describes Mount Everest:

Mount Everest document
{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

This document contains the following fields and values:

  • name: the peak’s name
  • height: the peak’s elevation, in meters
  • location: the countries in which the mountain is located. This field stores values as an array to allow for mountains located in more than one country
  • ascents: this field’s value is another document. When one document is stored within another document like this, it’s known as an embedded or nested document. Each ascents document describes successful ascents of the given mountain. Specifically, each ascents document contains a total field that lists the total number of successful ascents of each given peak. Additionally, each of these nested documents contain two fields whose values are also nested documents:
    • first: this field’s value is a nested document that contains one field, year, which describes the year of the first overall successful ascent
    • first_winter: this field’s value is a nested document that also contains a year field, the value of which represents the year of the first successful winter ascent of the given mountain

The reason why the first ascents are represented as nested documents even though only the year is included now is to make it easier to expand the ascent details with more fields in the future, such as the summiters’ names or the expedition details.

Run the following insertMany() method in the MongoDB shell to simultaneously create a collection named peaks and insert five sample documents into it. These documents describe the five tallest mountain peaks in the world:

  1. db.peaks.insertMany([
  2. {
  3. "name": "Everest",
  4. "height": 8848,
  5. "location": ["Nepal", "China"],
  6. "ascents": {
  7. "first": {
  8. "year": 1953
  9. },
  10. "first_winter": {
  11. "year": 1980
  12. },
  13. "total": 5656
  14. }
  15. },
  16. {
  17. "name": "K2",
  18. "height": 8611,
  19. "location": ["Pakistan", "China"],
  20. "ascents": {
  21. "first": {
  22. "year": 1954
  23. },
  24. "first_winter": {
  25. "year": 1921
  26. },
  27. "total": 306
  28. }
  29. },
  30. {
  31. "name": "Kangchenjunga",
  32. "height": 8586,
  33. "location": ["Nepal", "India"],
  34. "ascents": {
  35. "first": {
  36. "year": 1955
  37. },
  38. "first_winter": {
  39. "year": 1986
  40. },
  41. "total": 283
  42. }
  43. },
  44. {
  45. "name": "Lhotse",
  46. "height": 8516,
  47. "location": ["Nepal", "China"],
  48. "ascents": {
  49. "first": {
  50. "year": 1956
  51. },
  52. "first_winter": {
  53. "year": 1988
  54. },
  55. "total": 461
  56. }
  57. },
  58. {
  59. "name": "Makalu",
  60. "height": 8485,
  61. "location": ["China", "Nepal"],
  62. "ascents": {
  63. "first": {
  64. "year": 1955
  65. },
  66. "first_winter": {
  67. "year": 2009
  68. },
  69. "total": 361
  70. }
  71. }
  72. ])

The output will contain a list of object identifiers assigned to the newly-inserted objects.

Output
{ "acknowledged" : true, "insertedIds" : [ ObjectId("610c23828a94efbbf0cf6004"), ObjectId("610c23828a94efbbf0cf6005"), ObjectId("610c23828a94efbbf0cf6006"), ObjectId("610c23828a94efbbf0cf6007"), ObjectId("610c23828a94efbbf0cf6008") ] }

You can verify that the documents were properly inserted by running the find() method with no arguments, which will retrieve all the documents you just added:

  1. db.peaks.find()
Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } . . .

With that, you have successfully created the list of example documents of mountains that will serve as the test data for creating queries. Next, you’ll learn how to query with conditions referring to individual fields.

Step 2 — Querying Individual Fields

At the end of the previous step, you used MongoDB’s find() method to return every document from the peaks collection. A query like this won’t be very useful in practice, though, as it doesn’t filter any documents and always returns the same result set.

You can filter query results in MongoDB by defining a specific condition that documents must adhere to in order to be included in a result set. If you have followed the How To Perform CRUD operations in MongoDB tutorial, you have already used the most basic filtering condition: the equality condition.

As an example, run the following query which returns any documents whose name value is equal to Everest:

  1. db.peaks.find(
  2. { "name": "Everest" }
  3. )

The second line — { "name": "Everest" } — is the query filter document, a JSON object specifying the filters to apply when searching the collection in order to find documents that satisfy the condition. This example operation tells MongoDB to retrieve any documents in the peaks collection whose name value matches the string Everest:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

MongoDB returned a single document, as there is only one Mt. Everest in the peaks collection.

The equality condition specifies a single value that MongoDB will attempt to match against documents in the collection. MongoDB provides comparison query operators that allow you to specify other conditions that also refer to a single field, but filter documents in ways that are more complex than searching for exact matches.

A comparison operator consists of the operator itself, a single key preceded by a dollar sign ($), and the value the query operator will use to filter documents.

To illustrate, run the following query which searches for any documents whose name value does not equal Everest:

  1. db.peaks.find(
  2. { "name": { $ne: "Everest" } }
  3. )

This time, the query filter document includes { $ne: "Everest" }. $ne is the comparison operator in this example, and it stands for “not equal”. The peak name, Everest, appears again as the value for this operator. Because this query is searching for documents whose name value is not equal to Everest, it returns four documents:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } . . .

The $in operator allows you to write queries that will return documents with values matching one of multiple values held in an array.

The following example query includes the $in operator, and will return documents whose name value matches either Everest or K2:

  1. db.peaks.find(
  2. { "name": { $in: ["Everest", "K2"] } }
  3. )

Instead of a single value, the value passed to the $in operator is an array of two peak names in square braces. MongoDB returns two documents, just as expected:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

The examples so far have queried the name field with text values. You can also filter documents based on numerical values.

The following example query searches for documents whose height value is greater than 8500:

  1. db.peaks.find(
  2. { "height": { $gt: 8500 } }
  3. )

This query includes the $gt operator, which stands for greater than. By passing it the value 8500, MongoDB will return documents whose height value is greater than 8500:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

MongoDB offers a number of comparison query operators in addition to the ones outlined in this section. For a full list of these operators, see the official documentation on the subject.

Now that you know how to use equality conditions and comparison operators on a single document field, you can move onto learning how to join multiple conditions together in a single query.

Step 3 — Using Multiple Conditions

Sometimes, filtering based on a single document field is not enough to precisely select documents of interest. In such cases, you might want to filter documents using multiple conditions at once.

There are two ways to connect multiple conditions in MongoDB. The first is to use a logical AND conjunction to select documents in the collection matching all the conditions, or the logical OR to select documents matching at least one condition from the list.

In MongoDB, the AND conjunction is implicit when using more than one field in the query filter document. Try selecting a mountain that matches the name Everest and the exact height of 8848 meters:

  1. db.peaks.find(
  2. { "name": "Everest", "height": 8848 }
  3. )

Notice that the syntax is similar to the equality condition example from the previous step, but this time two fields appear in the query filter document. MongoDB checks for equality on both fields and requires both to match the requested values in order for a document to be selected:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

In this case, a single document is returned, but if you try changing the height to any other numerical value the result set will be empty since any returned documents must match both conditions. For instance, the following example will not return any output to the shell:

  1. db.peaks.find(
  2. { "name": "Everest", "height": 9000 }
  3. )

This implicit AND can be made explicit by including the $and logical query operator followed by a list of conditions that returned documents must satisfy. The following example is essentially the same query as the previous one, but includes the $and operator instead of an implicit AND conjunction:

  1. db.peaks.find(
  2. { $and: [{"name": "Everest"}, {"height": 8848}] }
  3. )

This time the JSON object containing the $and query operator is the query filter document itself. Here, the comparison operator takes two separate equality conditions that appear in the list, one for name matches and the latter for height matches.

In order to select documents matching any of the chosen conditions rather than all of them, you can instead use the $or operator:

  1. db.peaks.find(
  2. { $or: [{"name": "Everest"}, {"name": "K2"}] }
  3. )

When using the $or operator, a document only needs to satisfy one of the two the equality filters:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

Although each of this example’s conditions are single-field equality conditions, both the $and and $or operators can contain any valid query filter documents. They can even include nested AND/OR condition lists.

Joining multiple filters together using $and and $or operators as outlined in this step can be very helpful with retrieving fine-grained query results. However, the examples so far have all used query filter documents that filter based on individual values. The next step outlines how to query against values stored in an array field.

Step 4 — Querying for Array Values

Sometimes a single field may contain multiple values stored in an array. In our example with mountain peaks, location is such a field. Because mountains often span more than one country, like Kangchenjunga in Nepal and India, a single value may not always be enough for this field.

In this step, you’ll learn how to construct query filters that match items in array fields.

Let’s start by trying to select documents representing mountains that are in Nepal. For this example, though, it’s okay if the mountain has multiple locations listed, as long as one of them is Nepal:

  1. db.peaks.find(
  2. { "location": "Nepal" }
  3. )

This query uses an equality condition that tells MongoDB to return documents whose location value exactly matches the given string value, Nepal, similar to the previous examples that used the name field. MongoDB will select any documents in which the requested value appears in any place in the arrays:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

For this query, MongoDB returned the four documents in which Nepal appears in the location field.

However, what if you wanted to find mountains located in both China and Nepal? To do this, you could include an array in the filter document, rather than a single value:

  1. db.peaks.find(
  2. { "location": ["China", "Nepal"] }
  3. )

Even though there are four mountains in Nepal and China in the database, there is only one in which the countries are listed in the order given in this query, so this query returns a single document:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

Notice that the value of the location field for Makalu is identical to the query’s filter document. When you supply an array as the value for the equality condition like this, MongoDB will retrieve documents where the location field matches the query filter exactly, including the order of elements inside the array. To illustrate, run the query again but swap China with Nepal:

  1. db.peaks.find(
  2. { "location": ["Nepal", "China"] }
  3. )
Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

Now, two other mountains are returned, but Makalu is not.

Using the equality condition like this is not helpful in cases where you care only about elements in an array (regardless of their order) rather than an exact match. Fortunately, MongoDB allows you to retrieve documents containing more than one array element anywhere in an array using the $all query operator.

To illustrate, run the following query:

  1. db.peaks.find(
  2. { "location": { $all: ["China", "Nepal"] } }
  3. )

The $all operator will ensure that documents will be checked whether their location array contains both China and Nepal inside in any order. MongoDB will return all three mountains in a single query:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

This step outlined how to use arrays in query filter documents to retrieve documents with more than one value in a single field. If you want to query data held within a nested document, you’ll need to use the special syntax required for such an operation. Continue onto the next step to learn how to do this.

Step 5 — Querying Fields in Nested Documents

Recall that the example database documents include an ascent field that holds various details about each mountain’s first ascents an array. This way, the data about the first ascent, the winter ascent, and the total number of ascents is cleanly grouped inside a single nested document. This step explains how you can access fields within a nested document when building queries.

Review the sample Everest document once more:

The Everest document
{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

Accessing the name and height fields was straightforward, as a single value resides under these keys. But say you wanted to find the total number of ascents for a given peak. The ascents field contains more data than just the total number of ascents inside. There is a total field, but it’s not part of the main document, so there’s no way to access it directly.

To solve this issue, MongoDB provides a dot notation to access fields in nested documents.

To illustrate how MongoDB’s dot notation works, run the following query. This will return all the mountains in the collection that have been ascended more than 1000 times, using the $gt operator highlighted previously:

  1. db.peaks.find(
  2. { "ascents.total": { $gt: 1000 } }
  3. )

Mt. Everest is the only mountain in the collection with more than 1000 ascents, so only its document will be returned:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

While the { $gt: 1000 } query filter with $gt operator is familiar, notice how this query accesses the total field held within the document stored in the ascents field. In nested documents, the access path to any given field is constructed with dots indicating the action of going inside the nested object.

So, ascents.total means that MongoDB should first open the nested document that the ascents field points to and then find the total field within it.

The notation works with multiple nested documents as well:

  1. db.peaks.find(
  2. { "ascents.first_winter.year": { $gt: 2000 } }
  3. )

This query will return any documents describing mountains that were first ascended in winter only after the year 2000:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

As before, the ascents.first_winter.year notation means MongoDB first finds the ascents field and finds the nested documents there. It then goes into another nested document, first_winter, and finally retrieves the year field from within it.

The dot notation can be used to access any depth of nested documents in MongoDB.

By now you will have a good understanding of how to access data from nested documents and how to filter query results. You can move on to learning how to limit the list of fields returned by your queries.

Step 6 — Returning a Subset of Fields

In all the examples so far, whenever you queried the peaks collection, MongoDB returned one or more full documents. Oftentimes, you’ll only need information from a handful of fields. As an example, you might only want to find the names of the mountains in the database.

This isn’t just a matter of legibility, but also of performance. If only a small part of a document is needed, retrieving whole document objects would be an unnecessary performance burden on the database. This may not be a problem when working with small datasets like this tutorial’s examples, but it becomes an important consideration when working with many large, complex documents.

As an example, say you’re only interested in mountain names stored in the peaks collection, but the ascent details or location are not important this time. You could limit the fields your query will return by following the query filter document with a projection.

A projection document is a JSON object where keys correspond to the fields of the queried documents. Projections can be either constructed as inclusion projections or exclusion projections. When the projection document contains keys with 1 as their values, it describes the list of fields that will be included in the result. If, on the other hand, projection keys are set to 0, the projection document describes the list of fields that will be excluded from the result.

Run the following query, which includes the by-now familiar find() method. This query’s find() method includes two arguments, instead of one. The first, {}, is the query filter document. Here it’s an empty JSON object, meaning it won’t apply any filtering. The second argument, { "name": 1 }, describes the projection and means that the query results will only include each document’s name field:

  1. db.peaks.find(
  2. {},
  3. { "name": 1 }
  4. )

After running this example query, MongoDB returns the following results:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest" } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2" } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga" } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse" } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu" }

Notice that the returned documents are simplified, and contain only the name and _id fields. MongoDB always includes the _id key, even if it’s not explicitly requested.

To illustrate how to specify what fields to exclude, run the following query. It will return data from each document, but will exclude the ascents and location fields:

  1. db.peaks.find(
  2. {},
  3. { "ascents": 0, "location": 0 }
  4. )

MongoDB returns all five mountains once again, but this time only the name, height, and _id fields are present:

Output
{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848 } { "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611 } { "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586 } { "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516 } { "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485 }

Note: When specifying projections, you cannot mix inclusions and exclusions. You either have to specify the list of fields to include, or a list of fields to exclude.

There is, however, one exception to this rule. MongoDB allows you to exclude the _id field from a result set even when the query has an inclusion projection applied. To suppress the _id field, you can append "_id": 0 to the projection document. The following example is similar to the previous example query, but will exclude every field, including _id, except for the name field:

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1 }
  4. )
Output
{ "name" : "Everest" } { "name" : "K2" } { "name" : "Kangchenjunga" } { "name" : "Lhotse" } { "name" : "Makalu" }

Projections can also be used to include or exclude fields in nested documents. Say, for example, that you want to know each mountain’s first winter ascent and the total number of ascents, both of which are nested within the ascents field. Additionally, you want to return each mountain’s name. To do this, you could run a query like this:

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "ascents": { "first_winter": 1, "total": 1 } }
  4. )

Notice how the projection is specified for the ascents fields and how it follows the structure of the nested document, being a nested projection itself. By using "first_winter": 1, "total": 1 this query tells the database to include only these two fields from the nested document and no other.

The returned documents will contain only the requested fields:

Output
{ "name" : "Everest", "ascents" : { "first_winter" : { "year" : 1980 }, "total" : 5656 } } { "name" : "K2", "ascents" : { "first_winter" : { "year" : 1921 }, "total" : 306 } } { "name" : "Kangchenjunga", "ascents" : { "first_winter" : { "year" : 1988 }, "total" : 461 } } { "name" : "Makalu", "ascents" : { "first_winter" : { "year" : 2009 }, "total" : 361 } }

Limiting the size of returned documents to only a subset of fields can be helpful with making result sets more readable and can even improve performance. The next step outlines how to limit the number of documents returned by a query, and also details how to sort the data returned by a query.

Step 7 — Using Cursors to Sort and Limit Query Results

When retrieving objects from a large collection, there may be times when you want to limit the number of results or perhaps sort them in a particular order. For example, a popular approach for shopping sites is to sort products by their price. MongoDB uses cursors which allow you to limit the number of documents returned in a query result set and also sort the results in ascending or descending order.

Recall this example query from Step 1:

  1. db.peaks.find()

You may recall that the result set returned by this query includes all the data from each document in the peaks collection. While it may seem like MongoDB returns all the objects from the peaks collection, this is not the case. What MongoDB actually returns is a cursor object.

A cursor is a pointer to the result set of a query but it is not the result set itself. It’s an object that can be iterated, meaning that you can request the cursor to return the next document in line, and only then will the full document be retrieved from the database. Until that happens, the cursor only points to the next document on the list.

With cursors, MongoDB can ensure that the actual document retrieval happens only when it’s needed. This can have significant performance implications when the documents in question are large or many of them are requested at once.

To illustrate how cursors work, run the following operation which includes both the find() and count() methods:

  1. db.peaks.find().count()

MongoDB will respond with 5:

Output
5

Under the hood, the find() method finds and then returns a cursor, and then the count() method is called on that cursor. This lets MongoDB know that you’re interested in the object count and not the documents themselves. This means that documents won’t be a part of the results — all the database will return is the count. Using methods on the cursor object to further modify the query before retrieving documents from the cursor, you can ensure only the database operations that you ask for will be performed on the collection.

Note: When executing queries, the MongoDB shell automatically iterates over the returned cursors 20 times so as to display the first 20 results on the screen. This is specific to the MongoDB shell. When working with MongoDB programmatically, it won’t immediately retrieve any results from a cursor.

Another MongoDB method that uses cursors to alter a result set is the limit() method. As its name implies, you can use limit() to limit the number of results a query will return.

Run the following query which will retrieve only three mountain peaks from the collection:

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "height": 1 }
  4. ).limit(3)

MongoDB shell will respond with three objects rather than five, even though the query isn’t filtering any data:

Output
{ "name" : "Everest", "height" : 8848 } { "name" : "K2", "height" : 8611 } { "name" : "Kangchenjunga", "height" : 8586 }

The limit(3) method applied on the cursor tells the cursor to stop returning further documents after reaching the first 3. Using the limit() cursor method like this with large collections will help to ensure that you only retrieve the results you need and no more.

By default, MongoDB will return objects in the order of their insertion, but you might want to alter that behavior. Say you’re interested in finding the three lowest mountain peaks held in the database. You could run the following query:

  1. db.peaks.find(
  2. {},
  3. { "_id": 0, "name": 1, "height": 1 }
  4. ).limit(3).sort({ "height": 1 })

The added sort({ "height": 1 }) causes the result set to differ from the previous example:

Output
{ "name" : "Makalu", "height" : 8485 } { "name" : "Lhotse", "height" : 8516 } { "name" : "Kangchenjunga", "height" : 8586 }

Again, only three mountain peaks are returned. However, this time they have been sorted ascending from the one with the lowest height value.

The sort() method on the cursor accepts a JSON object — height — as an argument, similar to the projection document. It also accepts the list of keys that will be used to sort against. The accepted value is either 1 for ascending or -1 for descending sort order for each key.

Conclusion

By reading this article, you familiarized yourself with the way MongoDB uses to filter query results. You filtered collection documents against individual fields, multiple conditions, and complex structures such as arrays and nested documents. You have also learned to select only a subset of fields and sort the results using cursor methods. These techniques can be used to retrieve only documents of interest from otherwise large collections.

The tutorial described only a handful of query operators put forward by MongoDB to allow precise document querying. You can study the official official MongoDB documentation to learn more about different query operators.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


Tutorial Series: How To Manage Data with MongoDB

MongoDB is a document-oriented NoSQL database management system (DBMS). Unlike traditional relational DBMSs, which store data in tables consisting of rows and columns, MongoDB stores data in JSON-like structures referred to as documents.

This series provides an overview of MongoDB’s features and how you can use them to manage and interact with your data.

About the authors
Default avatar

Software Engineer, CTO @Makimo

Creating bespoke software ◦ CTO & co-founder at Makimo. I’m a software enginner & a geek. I like making impossible things possible. And I need tea.


Default avatar

Manager, Developer Education

Technical Writer @ DigitalOcean


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel