November 21, 2024

RavenDB how to model one-to-many relationships

One-to-many relationships are a standard norm in SQL databases. Going noSQL doesn’t mean you won’t be able to model such a relation. On the contrary, it is advisable to do so especially in cases where it is necessary. It will allow your database to save space since common data can be just referred to by unique ID and you can basically model database structure in a similar fashion as you would with a classic SQL database, retaining still the flexibility that noSQL offers.

With RavenDB, this one-to-many relationship design is pretty straightforward. Still, it may be tricky when you try to query the data since RavenDB has no concept of JOINs like SQL has. Let’s dive into it and I’ll explain how to implement it in the most efficient and elegant way.

Database structure

I will keep it as simple and concise as possible. If you had a look over my RavenNest tutorials you already know I used a movie database as a basis for exemplification. I will use the same movie database with the addition of actors as a separate entities in the “Actors” collection. In a simple diagram, this relationship would look similar to:

So, a movie can have one or more (many) actors. In practice, there’s no movie without actors as far as I know, but in database design you can have zero, one or many, no limitation whatsoever. And of course, an actor can have many movies, but to keep it simple let’s stick just to this one-to-many relation between movies and actors.

How would you model it in SQL?

Any model in SQL has an ID and by using foreign keys you can add a column inside the Actor table called movieId. Then if you want to query the movies also bringing data about the actors you simply write a query such as:


SELECT movie.*, actor.* FROM Movies movie
INNER JOIN Actors actor ON actor.movieId = movie.id
WHERE movie.id = <ID>

As you can see it is quite easy, no complications so far. Let’s go further and check the noSQL variant.

How would you model it in noSQL?

Although RavenDB has a query syntax similar to SQL, there are no inner joins like those in SQL. Instead, one has to rely on indexes in order to achieve the same result as the SQL query above. Indexes can be a challenging topic to handle, so I recommend checking out this article that I wrote about indexes for more clarification.

When it comes to data modeling, in RavenDB I foresee two options:

  1. Use a property called “movieId” in the Actor entity
  2. Add a property at the movie level called “actors” which is an array containing the IDs of all actors in the movie

You can use either of the options depending on your use-case or even both of them can be fine. I will use the second one for the purpose of this article. Everything will be made using RavenDB Studio, with a Javascript index (I think it’s the most suitable option).

Create the data and the index

With the current database, there are 4 movies. For each of them we will link 3 actors which will be defined as documents inside “Actors” collection. Each movie will contain the “actors” property with the corresponding 3 actor IDs in the array.


{
    "name": "Raven’s Hollow",
    "year": 2022,
    "tags": [
        "drama",
        "horror"
    ],
    "actors": [
        "d6fedbed-d65f-4bba-8fb5-37e70ac5be69",
        "711a5d8d-5b20-4a96-87f0-10c1697eb51e",
        "c9f2e463-ed80-43ce-a0f8-1e2304baf5ec"
    ],
    "@metadata": {
        "@collection": "Movies"
    }
}

Now that we have the data stored, it’s about time to create the index which will help perform our requested queries. The index is written in Javascript language, which is supported by RavenDB engine. Basically, it does take for an entity all actor IDs and loads the corresponding document and replaces the array with actors. Let’s have a look at it:


map('Movies', (doc) => {
    const actorIds = doc.actors;
    doc.actors = [];
    
    for (let i = 0; i < actorIds.length; i++) {
        doc.actors.push(load(actorIds[i], 'Actors'));
    }
    
    return doc
})

Not really that fancy, the JS indexes can be more complex if required. Just bear in mind that the ES syntax is a bit older in RavenDB and some features won’t work…

Then if we check in the Studio, we query the index with the raw entries expected:

We notice that RavenDB also brought to us the actors with their details. Still, there are same caveats to be aware of, but I will leave it for another time 😄

Conclusion

RavenDB is a great database offering for sure offering great flexibility and scalability. Do consider giving it a go for your project, the Javascript indexes really rock the place. There is some room for improvement and in practice things can get shaky, still, bear with me I have some solutions for you!

Thanks for reading, I hope you found this article useful and interesting. If you have any suggestions don’t hesitate to contact me. If you found my content useful please consider a small donation. Any support is greatly appreciated! Cheers  😉

afivan

Enthusiast adventurer, software developer with a high sense of creativity, discipline and achievement. I like to travel, I like music and outdoor sports. Because I have a broken ligament, I prefer safer activities like running or biking. In a couple of years, my ambition is to become a good technical lead with entrepreneurial mindset. From a personal point of view, I’d like to establish my own family, so I’ll have lots of things to do, there’s never time to get bored 😂

View all posts by afivan →