code_ablaze
code_ablaze

Reputation: 462

Get distinct pair of objects with all sub documents mongo meteor

I am using meteor and having difficulty solving this problem since 3 days. I searched over to stackoverflow but the answers didn't seem to satisfy my needs.I have a sales table with the following datas on them.

{
"_id" : 1,
"table" : "Table no 2",
"name" : "Hot Coffee",
"quantity" : 2,
"price" : "$10",
"seller" : "User",
"createdAt" : ISODate("2016-01-06T12:57:17.152Z")
},   
{
"_id" : 2,
"table" : "Table no 3A",
"name" : "Hot Coffee",
"quantity" : 1,
"price" : "$10",
"seller" : "User",
"createdAt" : ISODate("2016-02-01T12:58:17.152Z")
},
{
"_id" : 3,
"table" : "Table no 3A",
"name" : "Pizza",
"quantity" : 2,
"price" : "$50",
"seller" : "User",
"createdAt" : ISODate("2016-01-06T12:58:17.152Z")
},
 {
"_id" : 4,
"table" : "2A",
"name" : "Pizza",
"quantity" : 5,
"price" : "$50",
"seller" : "User",
"createdAt" : ISODate("2016-02-02T11:55:17.152Z")
},   

I am expecting distinct table names with all the quantity added

{
"name":"Hot Coffee",
"quantity": 3
},
{
"name":"Pizza",
"quantity": 7
}

I tried the distinct function but it seems to show a single result only.

Upvotes: 2

Views: 741

Answers (1)

chridam
chridam

Reputation: 103325

Use the aggregation framework for this, but because aggregate isn't supported yet in Meteor, you need to get the aggregation framework package installed - it doesn't do anything to fancy, just wraps up some Mongo methods for you.

Just meteor add meteorhacks:aggregate and you should be in business. This will add proper aggregation support for Meteor.

Now you would need this pipeline to achieve the desired result. In mongo shell, run the following:

var pipeline = [
    {
        "$group": {
            "_id": "$name",
            "quantity": { "$sum": "$quantity" }
        }
    },
    {
        "$project": {
            "name": "$_id", "_id": 0, "quantity": 1
        }
    }
];

db.sales.aggregate(pipeline);

Sample Output

{
    "result" : [ 
        {
            "quantity" : 7,
            "name" : "Pizza"
        }, 
        {
            "quantity" : 3,
            "name" : "Hot Coffee"
        }
    ],
    "ok" : 1
}

What this aggregation operation does is it uses the $group pipeline step to group all the documents by the name field, and with each distinct group you get the accumulated total quantity by using the accumulator operator $sum on the numerical field quantity for each group. The next pipeline $project will reshape the fields from the previous pipeline stream documents to the desired structure.

In Meteor, you can publish these results to the Sales collection on the client side using the following pattern, provided you have added the aggregate package to your meteor app:

Meteor.publish('getDistinctSalesWithTotalQuantity', function (opts) {

    var initializing = 1;

    function run(action) {

        // Define the aggregation pipeline ( aggregate(pipeline) )
        var pipeline = [
            {
                "$group": {
                    "_id": "$name",
                    "quantity": { "$sum": "$quantity" }
                }
            },
            {
                "$project": {
                    "name": "$_id", "_id": 0, "quantity": 1
                }
            }
        ];

        Sales.aggregate(pipeline).forEach(function(e){
            this[action]('distinct-sales', e.name, e)
            this.ready()
        });
    };

    // Run the aggregation initially to add some data to your aggregation collection
    run('added');

    // Track any changes on the collection we are going to use for aggregation
    var handle = Sales.find({}).observeChanges({
        added(id) {
            // observeChanges only returns after the initial `added` callbacks
            // have run. Until then, we don't want to send a lot of
            // `self.changed()` messages - hence tracking the
            // `initializing` state.
            if (initializing && initializing--) run('changed');
        },
        removed(id) {
            run('changed');
        },
        changed(id) {
            run('changed');
        },
        error(err) {
            throw new Meteor.Error('Aaaaaaaaah! Grats! You broke it!', err.message)
        }
    });

    // Stop observing the cursor when client unsubs.
    // Stopping a subscription automatically takes
    // care of sending the client any removed messages.
    this.onStop(function () {
        handle.stop();
    });
});

The above observes changes and if necessary reruns the aggregation.

Upvotes: 1

Related Questions