Learn the $elemMatch operator in MongoDB
MongoDB is an open-source document-oriented NoSQL database. We have introduced how to use mongosh
to run basic CRUD commands in the
console. Besides, we have also introduced how to use MongoDB Atlas to host your MongoDB server and data. In this post, we will take a step further and learn advanced MongoDB queries for nested documents using the $elemMatch
operator from practical examples. We will still use mongosh
to run the queries in the console so you don’t need to install anything
additionally. However, you can use a graphical IDE for MongoDB if you prefer to work in a visual environment.
Before we start this advanced journey, we should have a MongoDB server available. You can use Docker to start a MongoDB server in a container, or use MongoDB Atlas to start one that is hosted and maintained by Atlas. For simplicity, we will use a Docker container in this post, but you are free to choose the one you prefer. The commands to start a Docker container for MongoDB is:
$ docker network create mongo-net$ docker run --detach --network mongo-net --name mongo-server \
--env MONGO_INITDB_ROOT_USERNAME=admin \
--env MONGO_INITDB_ROOT_PASSWORD=pass \
--env MONGO_INITDB_ROOT_DATABASE=admin \
--volume mongo-data:/data/db \
--publish 27017:27017 \
mongo:5.0.6
You can
install mongosh
on your computer, or use the one coming together with the Docker container:
$ docker exec -it mongo-server bash
$ mongosh "mongodb://admin:pass@localhost:27017"
Now we can start to work with the MongoDB database using mongosh
. If this is the first time you work mongosh
, it’s recommended you
check this introductory post first so you won’t get lost in the advanced queries.
The data we will use in this post is some product data for an online shop of laptops, as demonstrated in the post for Elasticsearch. You can download this JSON file to get the raw data that will be used.
If you use the mongosh
inside the Docker container like me, you need to copy the JSON file into the container:
$ docker cp ./laptops_demo_for_mongodb.json mongo-server:/
And if you use the monosh
installed on your computer, you need to have this JSON file in your current working directory. Then we can
load this file and insert all the documents into a collection:
Key points for the code above:
- We use
use products
to declare a MongoDB database calledproducts
, which will be created when a document is inserted into a collection in it. - The JSON data is loaded by the
require
command. And the loaded data is in a format that can be used by theinsertMany[]
method directly. The laptops data is inserted into a collection calledlaptops
. - The
countDocuments[]
is used to check the number of documents in the collection and thefindOne[]
method is used to show the first document in the collection.
Now the data is ready, we can begin to write some queries for it.
As we see, the laptop documents in the laptops
collection have an attributes
field that is an array of embedded documents. It is more complex to query and update a field like this than simple
non-nested fields.
First, let’s find all the laptops whose CPU is Intel Core i7–8550U
. This should be pretty simple because the CPU is unambiguous:
Note that the nested field is queried with a dot notation and must be put in quotes. If you prefer to write queries spanning multiple lines with a graphical IDE, please refer to this post regarding the IDEs for MongoDB.
We should get the result we want because there is only one nested document in the attributes
array that has the value for CPU models:
Now let’s find all the laptops that have a memory of 16GB. Intuitively, you may want to use a query like this:
When the above query is executed, it seems all the laptops whose memory is 16GB are returned:
However, this is
where many beginners of MongoDB make mistakes and where some bugs are introduced into your code. If you enter “it” in mongosh
to show more results or just scroll down close to the bottom of the result page with an IDE and check carefully, you will find something strange:
We get the laptops whose storage is 16GB as well with the query above! This is because the above query finds the documents where the attributes
array has at least one embedded document that
contains the field attribute_name
equal to memory
and at least one embedded document [but not necessarily the same embedded document] that contains the field attribute_value
equal to 16GB
.
Since all the attributes
arrays have an embedded document whose attribute_name
is memory
, the query above gave use erroneous results. What we want is that both conditions should be satisfied for the same embedded document. To achieve this, we cannot query by dot notation as shown above but need to use the
$elemMatch
operator:
This time the laptops whose storage is 16GB but memory is not 16GB will not be returned. If you don’t believe it, you can count the number of documents that are returned by both queries:
Now let’s try an even more complex case and find all the laptops whose memory is 16GB and storage is 1TB. We would need to use the $and
operator to specify the conditions for two nested documents, which is very
similar to the usage of the bool
and must
keywords in Elasticsearch for the query of nested documents.
This query will return the result we want:
Note that even though the $and
operator is the default one in MongoDB, it is mandatory
here because we are querying the same field in two different conditions. Otherwise, we will only query by the second condition and will get incorrect results:
You will get one incorrect result in this case. If you try with other conditions, you will get more incorrect results.
Once you know how to query an array with nested documents, it should be fairly easy to update it. Note that the nested documents are ordered in the array and therefore you can access a nested document by the index position.
Let’s update the memory to 16GB for the laptop with _id
equal to 1:
If we check this laptop now, its attributes should have been updated:
Here we use projection to just show the attributes
field of the document:
Cheers! The nested document is updated successfully!
In this post, we have introduced how to query and update an array of nested documents in MongoDB. This is an advanced usage but is very common in practice. I’m sure you are fairly confident in working with this kind of issue in your work now.