Mastering TypeORM: Solutions to Limiting Relation Join and Agregation Operation

Ayoub ToubaAyoub Touba
4 min read

TypeORM is a powerful Object-Relational Mapping (ORM) library for TypeScript and JavaScript applications. It simplifies the interaction between your application and the underlying database, allowing you to work with entities and relationships instead of raw SQL queries. However, as your application grows in complexity, you may encounter scenarios where you need to perform more advanced queries or manipulations.

In this article, we'll explore two common questions from Stack Overflow related to TypeORM and provide detailed explanations and solutions to help you master these scenarios.

Question 1: How to Limit Children While Left Joining in TypeORM?

One common requirement in many applications is to fetch data from multiple related entities while limiting the number of child entities returned. This is especially useful when dealing with scenarios like displaying a limited number of comments for a blog post or showing a preview of recent activities.

Here's a solution to this problem using TypeORM:

// 1. Fetch all users
const users = await this.userRepository
  .createQueryBuilder("user")
  .getMany();

// 2. Loop through users and fetch photos with limit
for (const user of users) {
  user.photos = await this.photoRepository
    .createQueryBuilder("photo")
    .where("photo.userId = :userId", { userId: user.id })
    .limit(5) // Set the desired limit here
    .getMany();
}

// Return users with limited photos

In this example, we first fetch all users using the userRepository. Then, we loop through each user and fetch the associated photos using the photoRepository. The key step here is the .limit(5) clause, which limits the number of photos returned to 5 for each user.

This approach involves two separate queries: one to fetch all users, and another to fetch the limited photos for each user. While this solution works, it may not be the most efficient approach, especially if you have a large number of users or photos.

An alternative approach could be to use a single query with a subquery to fetch the limited photos for each user:

const users = await this.userRepository
  .createQueryBuilder("user")
  .leftJoinAndSelect(
    "user.photos",
    "photo",
    "photo.id IN (SELECT p.id FROM Photo p WHERE p.userId = user.id ORDER BY p.createdAt DESC LIMIT 5)"
  )
  .getMany();

In this example, we use the leftJoinAndSelect method to fetch the users and their associated photos in a single query. The subquery SELECT p.id FROM Photo p WHERE p.userId = user.id ORDER BY p.createdAt DESC LIMIT 5 selects the top 5 most recent photos for each user based on the createdAt column. The IN clause then filters the photos to only include those returned by the subquery.

This approach can be more efficient as it reduces the number of database queries, but it may be more complex to write and maintain, especially for more intricate scenarios.

Question 2: How to Use TypeORM's SUM Operator on a Relation's Field?

Another common requirement in applications is to perform aggregations, such as calculating the sum, average, or count of values from related entities. For example, you might want to calculate the total duration of all songs in a playlist.

Here's how you can achieve this using TypeORM:

const playlist = await this.playlistRepository
  .createQueryBuilder("playlist")
  .where("playlist.id = :playlistId", { playlistId })
  .leftJoinAndSelect("playlist.songs", "songs")
  .addSelect("SUM(songs.duration)", "totalDuration")
  .getRawOne();

console.log(playlist.totalDuration);

In this example, we use the createQueryBuilder to build a query for the playlistRepository. We start by filtering for a specific playlist using the where clause.

Next, we use the leftJoinAndSelect method to include the related songs entities in the query. This allows us to access the duration field of each song.

The key step here is the addSelect("SUM(songs.duration)", "totalDuration") clause. This tells TypeORM to calculate the sum of the duration field from all related songs and alias the result as totalDuration.

Finally, we use the getRawOne method to execute the query and retrieve the result as a raw object. The totalDuration property of this object will contain the calculated sum of all song durations for the specified playlist.

It's important to note that getRawOne and getRawMany are used when you need to perform aggregations or other advanced SQL operations that cannot be easily represented using TypeORM's entity model.

Conclusion

TypeORM is a powerful tool that simplifies database interactions in TypeScript and JavaScript applications. However, as your application's requirements grow more complex, you may encounter scenarios where you need to perform advanced queries or manipulations.

In this article, we explored two common questions from Stack Overflow related to TypeORM: limiting the number of child entities returned while left joining, and using the SUM operator on a relation's field. We provided detailed explanations and solutions to help you master these scenarios and unlock the full potential of TypeORM in your applications.

Remember, while TypeORM abstracts away much of the complexities of working with databases, it's still essential to have a solid understanding of SQL and query optimization techniques. By combining the power of TypeORM with your SQL knowledge, you can build robust and efficient applications that meet even the most demanding requirements.

0
Subscribe to my newsletter

Read articles from Ayoub Touba directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ayoub Touba
Ayoub Touba

With over a decade of hands-on experience, I specialize in building robust web applications and scalable software solutions. My expertise spans across cutting-edge frameworks and technologies, including Node.js, React, Angular, Vue.js, and Laravel. I also delve into hardware integration with ESP32 and Arduino, creating IoT solutions.