MongoDB basics for everyone – Part 7 – Arrays and embedded documents

One of the big things that you will notice when you start working with MongoDB, coming from a relational world, is the lack of joins. Joins are fun and all, but as we all probably know, not very scalable. This means that we usually end up doing “joins” in our applications anyway. MongoDB has no support for joins, although you do have access to so called “dbrefs”, which we usually do not make use of due to their [lack of] speed.

We can create a relation to another document of course, which also makes use of indexes and all the good things that we need in our apps, but this usually requires two

find()

commands to be executed. As an example, we could make a rudimentary “likes” collection:

use likes
db.things.insert({"_id":"thing1", "thing":"ice-cream"});
db.things.insert({"_id":"thing2", "thing":"cookies"});
db.things.insert({"_id":"thing3", "thing":"pumpkin"});

Let us assume we also have a people (users) collection:

db.users.insert({"_id":"user1", "name": "Paul"});
db.users.insert({"_id":"user2", "name": "John"});
db.users.insert({"_id":"user3", "name": "Fred"});

So the problem here is to relate the things that the users like to the users in some way. There are a number of ways to achieve this, without using joins!

Example 1: Using arrays:

We will simply create an array of the things that the users like within the user document. We can either do that as a String of the thing that the user likes, or as an array of the document ID of the thing (which will make maintenance a bit easier, as you only have to change the fields in one place)

db.users.update({"_id":"user1"}, { $set: {"likes":["thing1", "thing2"]}});

You will notice that I have used the OjectID’s of the “things” collection.

OR

db.users.update({"_id":"user2"}, { $set: {"likes":["ice-cream", "cookies"]}});

In the above example, I have simply set an array of things that I like. Please note that this is not as maintainable as the first example, due to the fact that if I update “ice-cream” to “icecream”, for example, I will be forced to to an atomic update across the entire “users” collection, which may take some time if I have a few million users.

A third way of approaching the problem is to embed another document within my document. Remember that MongoDB documents are limited to a size of 16MB, so this may not be the best option for you, but in less data intensive collections (i.e. without images, video, or other GridFS types), it should do just fine!

db.users.update({"_id":"user3"}, { $set: {"likes":{"thing1":"ice-cream", "thing2":"cookies"}}});

Which will give me an embedded JSON document within my “user” document about user “Paul”.

Do a quick

db.users.find().pretty()

to view your handy work!

We would now like to query our shiny new collection to find all the users that like “cookies”

The methods to do so are as below (we are also introducing the $in operator for working with arrays):

db.users.find({"likes":{$in:["thing2"]}})

which will return

{ "_id" : "user1", "likes" : [ "thing1", "thing2" ], "name" : "Paul" }

Next, we work with the array of things that were named (user2):

db.users.find({"likes":{$in:["cookies"]}})

which returns

{ "_id" : "user2", "likes" : [ "ice-cream", "cookies" ], "name" : "John" }

Finally, the embedded document query:

db.users.find({"likes.thing2":"cookies"})

which will return

{ "_id" : "user3", "likes" : { "thing1" : "ice-cream", "thing2" : "cookies" }, "name" : "Fred" }

The key here is that although we have the power of dynamic schema within MongoDB, you still want to think about schema design and plan with that 16MB document size limit in mind!

Summary
Author Rating
4
Software Name
MongoDB
All
all
0
0
Landing Page

Liked this post? Follow this blog to get more. 

  • Fahim Sabir

    Thank you for the article.

    The part that I have struggled with is what approach I should take if I want to return only the matching sub-documents/array members along with the parent document as opposed to the parent document and all sub-documents/array members when only some match my query. Does a particular modelling method make this any easier/possible?

    • Paul Scott

      Fahim,

      There are a few ways to approach your question. I am a little unclear about what you want to do, but to exclude certain fields from the returned document, you can pass in {“fieldname”:-1}, which will exclude it from the returned results.

      • Fahim Sabir

        Paul –
        I guess it is best explained with an example.

        If I have a document that looks like
        {
        id: 123,
        name: ‘John’,
        likes: [
        { type: ‘food’, name: ‘curry’ },
        { type: ‘food’, name: ‘bread’ },
        { type: ‘tools’, name: ‘spanner’}]
        }

        {
        id: 124,
        name: ‘Bill’,
        likes: [
        { type: ‘sites’, name: ‘amazon.com’ },
        { type: ‘food’, name: ‘bread’ },
        { type: ‘tools’, name: ‘spanner’}]
        }

        How do I write a query that brings back the foods that are liked by people that like the tool spanner, but only bringing back the foods, so the result of the query is:
        {
        id: 123,
        name: ‘John’,
        likes: [
        { type: ‘food’, name: ‘curry’ },
        { type: ‘food’, name: ‘bread’ }]
        }

        {
        id: 124,
        name: ‘Bill’,
        likes: [
        { type: ‘food’, name: ‘bread’ }]
        }

        Or do I filter it in the client, or model this data differently?

        Hope the example makes sense!

        • Paul Scott

          I think that the best way to approach this would be to use $project in the Aggregation Framework, which I will cover a bit later. The simpler way of *nearly* getting there would be something like:

          db.eg.find({“likes.type”:”food”, “likes.name”:”spanner”},{“name”:1,”likes.name”:1}).pretty() or thereabouts. You can experiment with selectors and hiding/adding certain fields as you like.

          I will cover Aggregation soon, I am just extremely busy with work at the moment, so it is a bit difficult! :)