Skip to main content

GROUP BY IN MONGOOSE WITH LOOKUP

Group ? Populate ? Lookup ? If you too are like me and come from the SQL world of JOINS and GROUP BY , you might have a bit of a trouble getting used to these operations in MongoDB or maybe this is the first time you are looking into something like this. Either way, let us in this article see how Group By and Lookup work in Mongoose.

GROUP

So, first of all I assume that you guys know what Group or Group By (In SQL) actually is. This is an operator that allows you to group the documents or model instances in the case of mongoose using some field and display rest of the information based on that information.
Let us take an example here. Suppose there is a Post model which has many comments, these comments are stored in a comments array as Object IDs. Also, this Post model has other attributes like category , author , views etc.
What we want to achieve is to for example display the total number of views for a particular author and also load all the comments that that particular author’s posts have received. So , here we will look to group the posts by authors , match the author name you want the information for (if there is an Author model, who knows ?) . We will then use the lookup operator that will fetch the comments from the comment model. 
Note -> The lookup operator works like the populate method in mongoose, the difference being the lookup method is used in aggregates.
Let’s see how do we achieve this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
models.Post.aggregate([
  {"$group": { "_id": "$author"},
       "totalAuthorViews" : {$sum : "$views"},
       "comments" : {$push : "$comments"}
  },
  {
    "$lookup": {
      "from": "comments",
      "localField": "comments",
      "foreignField": "_id",
      "as": "comments"
    }
  },
  {
    "$unwind": {"path": "$comments"}
  },
  {
    "$limit": 10
  }
])
Now, there are tens of other methods and options that we could have added to the aggregate above but we have more or less achieved what we set out to. To use others you may refer this link -> https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/
What you could also add is a match with a query, add skip with a well… a number to be added as a skip value.
Actually, this was the thing that made me use aggregate as well, the mongoose methods distinct and skip do not work together. Therefore, I used group to achieve it using aggregate.

Lookup

Now we know what Group does, what does Lookup do? If you have used MongoDB or other document based databases you must be knowing that we often store the ObjectID of some other document as a field value. To get the fields for the other document while querying for the first document we use the lookup operator.
Actually, this is what even populate does under the hood for mongoose. And for all my SQL friends, this is the replacement for JOINS in Mongo.

Code Explanation

We saw a piece of code up that used group in aggregate instead of mongoose methods and lookup instead of populate.
Let us break down that code line by line and see what it does.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
models.Post.aggregate([
  {"$group": { "_id": "$author"},
       "totalAuthorViews" : {$sum : "$views"},
       "comments" : {$push : "$comments"}
  },
  {
    "$lookup": {
      "from": "comments",
      "localField": "comments",
      "foreignField": "_id",
      "as": "comments"
    }
  },
  {
    "$unwind": {"path": "$comments"}
  },
  {
    "$limit": 10
  }
])
Here is the code for your convenience yet again.
1. The first block inside the aggregate is the group block, what this does is create a group and determines what fields will that group project.
The _id of the group is to be set to the field you want to GROUP BY.
The other fields are which you want to project.
In the example, we have author as the _id because we want to group using the author. The other two fields being projected are views and comments. Pay close attention to comments here.
2. The next block is that of lookup. What lookup does is take four arguments -> from , localField, foreignField and as. 
from is the document you want to pick the fields from, localField is the field value you want to join using in your own document. foreignField is the same value in the ‘from’ document you want to join with. as is just an alias to be shown as in the result.
3.The unwind operator basically takes the array equivalent of the value and returns a mongo document.
A very important thing to remember is that the aggregate works as a pipeline and that the second block receives the result of the first block as the argument.
So, the first block’s result must contain the field you are using as the localField in the lookup.
This is the exact reason why we have first pushed the comment IDs to the comments field for the first block’s result.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...