Count the nested fields in a MongoDB collection using Meteor

I need assistance in building a dashboard to display order data summaries within the application. Specifically, I am looking to count the number of items in a particular category within my Orders collection. Here is an example of the data structure:

Collection data

{
    "_id" : "a6wHiXxyM5DwSAsfq",
    "orderNumber" : 1234,
    "createdAt" : "11/01/2016, 14:43:49",
    "productsInOrder" : [
        {
            "category" : "ambient",
            "item" : 50818,
            "desc" : "Tasty Rubber Chicken",
            "quantity" : "44",
            ...
        },
        {
            "category" : "frozen",
            "item" : 71390,
            "desc" : "Generic Granite Fish",
            "quantity" : "11",
            ...
        }
    ]
}
...

JS

Here are some examples of what I have tried so far:

Orders.find({ 'productsInOrder': ['ambient']}).count();
Orders.find({ productsInOrder: { category: 'ambient' }}).count();
Orders.find({ productsInOrder: { $all: [ 'frozen' ] }}).count();

I'm struggling with understanding nested Mongo queries like this. Any guidance on how to approach this would be greatly appreciated. Thank you.

* SOLUTION *

I was able to achieve the desired outcome thanks to the help provided below. To implement this, I created a server method since the query cannot run directly on the client using an existing collection. Here's how it's done:

Meteor.methods({
  'byCategory': function() {
    var result = Orders.aggregate([
        { "$unwind": "$productsInOrder" },
        {
            "$group": {
                "_id": null,
                "ambient_count": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$productsInOrder.category", "ambient" ] }, 1, 0 ]
                    }
                },
                "frozen_count": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$productsInOrder.category", "frozen" ] }, 1, 0 ]
                    }
                },
                "other_category_count": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$productsInOrder.category", "other_category" ] }, 1, 0 ]
                    }
                }
            }
        }
    ]);

    return result;
  }
})

Then, on the client side:

Meteor.call('byCategory', function( error, result ) {
  if( error ) {
    console.log( error.reason );
  } else {
    console.log( result[0].ambient_count );
    console.log( result[0].frozen_count );
    etc....
  }
});

Credits and thanks to @chridam and @Brett for their contributions.

Answer №1

A different strategy involves utilizing the aggregation framework. Take into account the following aggregation pipeline: as the initial stage, the $unwind operator normalizes the productsInOrder array to produce a number of output documents equal to the number of elements in the array for each input document. Subsequently, the $group operator groups all documents into a single document, storing category counts using the $sum and $cond operators.

In Meteor, you can employ the meteorhacks:aggregate package to execute the aggregation:

To add to your app, use the command:

meteor add meteorhacks:aggregate

Take note that this feature only functions on the server side and lacks observing support or built-in reactivity. Simply employ the .aggregate function like so.

var coll = new Mongo.Collection('orders');
var pipeline = [
    { "$unwind": "$productsInOrder" },
    {
        "$group": {
            "_id": null, 
            "ambient_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$productsInOrder.category", "ambient" ] }, 1, 0 ]
                }
            },
            "frozen_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$productsInOrder.category", "frozen" ] }, 1, 0 ]
                }
            },
            "other_category_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$productsInOrder.category", "other_category" ] }, 1, 0 ]
                }
            }
        }
    }       
];
var result = coll.aggregate(pipeline);

Executing the same pipeline in the mongo shell with sample data will yield:

{
    "result" : [ 
        {
            "_id" : null,
            "ambient_count" : 1,
            "frozen_count" : 7,
            "other_category_count" : 0
        }
    ],
    "ok" : 1
}

You have the option to access the native mongo collection and publish the aggregation results to the client-side orders collection:

Meteor.publish('categoryCounts', function() {  
    var self = this,
        db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    orders = db.collection("orders").aggregate(pipeline, // Wrap the callback to ensure it runs in a Fiber.
        Meteor.bindEnvironment(
            function(err, result) {
                // Add each result to the subscription.
                _.each(result, function(e) {
                    self.added("orders", e._id, e);
                });
                self.ready();
            },
            function(error) {
                Meteor._debug( "Error doing aggregation: " + error);
            }
        )
    );
});

Answer №2

If you prefer not to carry out this task within the Meteor framework, then you will need to utilize mongo aggregation. Unfortunately, Minimongo does not support aggregation out of the box, so you must incorporate this package to achieve it:

https://docs.mongodb.org/manual/core/aggregation-introduction/

I only verified this in mongo itself, so you will need to adjust it according to how the aggregation package functions:

db.orders.aggregate([
    {
        $unwind: "$productsInOrder"
    }, 
    {
        $match: {
            "productsInOrder.category": "frozen"
        }
    },
    {
        $group: {
            _id: null, 
            count: {
                $sum: 1
            }
         }
     }
]);

The first step involves unwinding the collection, essentially creating an "order" entry for every instance of $productsInOrder. After flattening the array, we filter based on the desired category (e.g., "frozen"). Subsequently, we group the results to calculate the number of documents returned. The $group operation constructs the final output object from the query. You have the flexibility to customize this process as needed, such as grouping by productsInOrder.category without specifying a specific match condition like "frozen".

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Developing a universal.css and universal.js file for a custom WordPress theme

I have developed a custom WordPress theme with an extensive amount of code. To manage the numerous style and script files, I have segmented them into multiple individual files. To integrate all these files into my template, I utilized the following code w ...

Utilize JavaScript to encapsulate specific data into a JSON array

I need help converting a string value into JSON format: hello|world|this|is|javascript || my|name|is|mahdi My current code successfully splits the string using ||, but I am unable to group the split values into separate arrays in the JSON output. Here is ...

What is the best way to reset a dropdown list value in angular?

Is there a way to erase the selected value from an Angular dropdown list using either an x button or a clear button? Thank you. Code <div fxFlex fxLayout="row" formGroupName="people"> <mat-form-field appearance=&quo ...

What is the process for modifying a value within a dictionary located within an array in MongoDB?

{ "_id": 1359185710985371235, "main": 2, "streamers": [{"name": "me", "count": 1},{"anothername", "count": 0}] } Hello, I'm facing an issue with MongoDB and PyMongo. Specifically, I need to make changes to the "count" value within the "st ...

How to detect the Back Button or Forward Button events in a single-page application

Currently, I am developing a single-page application that utilizes ASP.NET MVC, Backbone.js, and JQuery. My task involves capturing the browser's back and forward button events for breadcrumb implementation. I previously attempted to use the hashchan ...

What is the best way to resize my hamburger menu and achieve a flawlessly circular border for it?

I've been struggling to reduce the size of my hamburger menu (both height and width) for some time now. I managed to make it a bit smaller, but I can't seem to figure out how to shrink it further. Additionally, I'm having trouble creating a ...

Observing the data retrieved from an AJAX request

Currently, I have this AJAX call in my code: $('#search').keyup(function() { $.ajax({ type: "GET", url: "/main/search/", data: { 'search_text': $('#search').val() }, suc ...

Restarting MongoDB Automatically on Ubuntu

My current setup has mongodb set to autoload in systemctl. Unfortunately, when I send a high number of requests to it, it crashes and shows the mark failed. Is there a way to configure mongodb to auto-restart so that it can recover from errors and contin ...

A guide on breaking down the ID passed from the backend into three segments using React JS

I pulled the data from the backend in this manner. https://i.stack.imgur.com/vMzRL.png However, I now require splitting this ID into three separate parts as shown here. https://i.stack.imgur.com/iy7ED.png Is there a way to achieve this using react? Bel ...

How do I record a native view as a video using React Native?

Is there a way to record the native view as video in React Native, similar to screen recording but focused only on the specific View? ...

By default, the text area element in Vue or Nuxt will send a message to the console

Every time I include a textarea html element in my Vue/cli or Nuxt projects, this message appears in the console. Is there a way to prevent this message from showing up in the console? <textarea rows="5" cols="33"> This is a textarea. < ...

Tips for adjusting border colors based on the current time

Trying to change the border color based on the opening hours. For example, green border from 10am to 9:29pm, yellow from 9:30pm to 9:44pm, and orange from 9:45pm until closing at 10pm. The issue is that the colors change at incorrect times beyond midnight. ...

Can you explain the purpose of 'cb' in the multer module

Within the code snippet below, taken from the multer API, both the destination and filename options consist of anonymous functions. These functions contain an argument named cb. I am curious whether these callback functions are defined within the multer ...

Exploring touch interactions using D3.js and TUIO

I'm currently facing a challenge with implementing multi-touch functionality and the d3.slider in my D3 Map. You can see the current state of my project in this video. With the d3 slider, I can spawn points and navigate around the map using touch even ...

Position the component at the center of another component

Looking to center a component inside another component. I have a div with two components - the main one and a banner that I want to display in the center of the main component. Check out my issue on this jsfiddle: https://jsfiddle.net/CanadianDevGuy/vjb4k ...

Error on Heroku: Module 'Mongoose' not located

After being deployed on Heroku for about 1.5 years, my instance suddenly threw an error related to 'mongoose' not being found when attempting to deploy again. Strangely, everything works fine on my local server and my .gitignore file excludes Nod ...

Struggling with long query times in MongoDB?

I am struggling with a collection that has over 550,000,000 documents and it's taking too long to search through. The collection only contains one field and ID, so I need to find a way to efficiently query and return the relevant documents. Here is a ...

What is the best way to create a function that triggers another function every minute?

Currently, I have a function that checks if a user is authenticated: isAuthenticated = (): boolean => { xxx }; I am working with AngularJS and I want to create a new function called keepCheckingAuthentication() This new function should call the ...

Having difficulty transitioning Express Controller logic to a Service Class

Within the User Controller, there is a function that processes a GET request to /user/ and retrieves two JSON objects randomly from a MongoDB collection. Here is how the response looks like inside the controller: [{ "name": "User 1" ...

The Bootstrap navbar is not aligning correctly and the collapse feature is not functioning properly

My first experience with Bootstrap is not going as expected. I am trying to create a navigation bar where the links align to the right, and when the screen size decreases, it changes to a hamburger menu on the right side. However, the links do not move to ...