Meteor Mongodb:统计嵌套对象中的字段数量

3
我将尝试创建一个仪表板,展示应用程序中订单数据的摘要。在这种情况下,我只想计算我的订单集合中给定类别的物品数量。到目前为止,我的代码如下所示:

集合数据

{
    "_id" : "a6wHiXxyM5DwSAsfq",
    "orderNumber" : 1234,
    "createdAt" : "11/01/2016, 14:43:49",
    "productsInOrder" : [
        {
            "category" : "ambient",
            "item" : 50818,
            "desc" : "Tasty Rubber Chicken",
            "quantity" : "44",
            "price" : "0.92",
            "lineprice" : "40.48",
            "_id" : "FFNxG8vujs6NGN69r"
        },
        {
            "category" : "frozen",
            "item" : 71390,
            "desc" : "Generic Granite Fish",
            "quantity" : "11",
            "price" : "1.00",
            "lineprice" : "11.00",
            "_id" : "LcRtpyLxkWyh39kkB"
        }
    ]
}
{
    "_id" : "PdpywXCvfew7qojmA",
    "orderNumber" : 1234,
    "createdAt" : "11/01/2016, 14:44:15",
    "productsInOrder" : [
        {
            "category" : "frozen",
            "item" : 71390,
            "desc" : "Generic Granite Fish",
            "quantity" : "44",
            "price" : "1.00",
            "lineprice" : "44.00",
            "_id" : "dAscx4R8pcBgbzoZs"
        },
        {
            "category" : "frozen",
            "item" : 66940,
            "desc" : "Gorgeous Granite Bike",
            "quantity" : "55",
            "price" : "4.21",
            "lineprice" : "231.55",
            "_id" : "xm3mFRmPmmdPxjfP9"
        },
        {
            "category" : "frozen",
            "item" : 96029,
            "desc" : "Gorgeous Plastic Fish",
            "quantity" : "1234",
            "price" : "4.39",
            "lineprice" : "5417.26",
            "_id" : "7u7SHnpTf7PWcrhGA"
        }
    ]
}
{
    "_id" : "xcHZ25qwvyDpDJtAZ",
    "orderNumber" : 1234,
    "createdAt" : "11/01/2016, 14:44:47",
    "productsInOrder" : [
        {
            "category" : "frozen",
            "item" : 31104,
            "desc" : "Handcrafted Rubber Keyboard",
            "quantity" : "11",
            "price" : "4.78",
            "lineprice" : "52.58",
            "_id" : "LMMwbKFEgnCbgCt9c"
        },
        {
            "category" : "frozen",
            "item" : 77832,
            "desc" : "Practical Rubber Shirt",
            "quantity" : "21",
            "price" : "0.62",
            "lineprice" : "13.02",
            "_id" : "63otkkXWGrTJkwEgX"
        },
        {
            "category" : "frozen",
            "item" : 66940,
            "desc" : "Gorgeous Granite Bike",
            "quantity" : "111",
            "price" : "4.21",
            "lineprice" : "467.31",
            "_id" : "rbPSujey8CFeMPjza"
        }
    ]
}

JS

到目前为止,我尝试过以下方法:

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

我在数据嵌套时很难理解Mongo查询。请帮我指点一下方向,非常感谢。
* 解决方案 *
感谢以下贡献者的帮助,我已经获得了所需的结果。为了使其工作,我在服务器上创建了一个方法,因为无法在客户端使用现有集合运行查询。具体如下:
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;
  }
})

然后在客户端执行:
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....
  }
});

感谢并致谢@chridam和@Brett的贡献。

我相信有一种简单的方法可以按类别(冷冻、常温等)进行分组,然后计数。在mongo中尝试这个:db.orders.aggregate([{$unwind: "$productsInOrder"}, {$group: {_id: "$productsInOrder.category", count: {$sum: 1}}}]); 对于您的目的,您需要在meteor中执行以下操作:Orders.aggregate([{"$unwind": "$productsInOrder" },{"$group": {"_id": "$productsInOrder.category","count": {"$sum": 1}}}]); - Brett McLain
谢谢@Brett,我会尝试并回报的,这似乎是一个更简单的解决方案。 - mtwallet
2个回答

3

另一种方法是使用聚合框架。考虑以下聚合管道,作为聚合管道的第一个阶段,$unwind运算符对productsInOrder数组进行去规范化,以便为每个输入文档输出个文档,其中n是数组元素的数量。下一个管道阶段具有$group操作符,该操作符将所有文档分组成一个单独的文档,并借助$sum$cond操作符存储每个类别的计数。

在Meteor中,您可以使用meteorhacks:aggregate包来实现聚合:

通过以下方式将其添加到您的应用程序中

meteor add meteorhacks:aggregate

请注意,这仅适用于服务器端,没有内置的观察支持或反应性。然后,只需像下面这样使用.aggregate函数。

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);

使用示例数据在mongo shell中运行相同的管道将产生以下结果:

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

您可以在客户端访问本地mongo集合,并将聚合结果发布到orders集合中:

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

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

谢谢您的回复,有没有办法在现有的集合上使用它?如果我按照您上面所说的方法使用,会出现集合已存在的错误。或者,如果我只是运行 Orders.aggregate(),会出现聚合不是函数的错误。我已经正确安装了所有依赖包。 - mtwallet
@mtwallet 只需添加 meteorhacks:aggregate,你就可以开始使用了。这将为你的集合添加一个聚合方法。我还提供了另一种方法,你可以访问 mongodb 原生驱动程序,从而使用底层方法来运行聚合操作。 - chridam

2

如果您不想在Meteor中执行此操作,则需要使用mongo聚合。但是,Minimongo不包括聚合功能,因此您需要使用此软件包来完成:

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

我只在mongo本身中测试过这个功能,因此您需要根据聚合软件包的方式进行调整:

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

首先要拆分集合。这基本上会为每个$productsInOrder实例创建一个“订单”条目。一旦将数组展开,我们就会匹配你关心的类别;在本例中,是“冰冻”类别。接下来,我们将其分组以便计算返回的文档数。$group只是构造查询输出的最终对象。你可以修改它成为任何你想要的形式,或者你可以按productsInOrder.category进行分组,而不需要$match“冰冻”。


嗨@Brett,感谢你的帮助,我很感激。当我尝试这个时,我得到了一个值为三的返回结果。目前集合中共有7个实例。就好像它只从每个实例中拉出一个“frozen”一样。我将更新我的集合以反映我所说的内容。 - mtwallet
啊,那么我发的查询只会按订单逐项查找……让我想出一个新的解决方案。 - Brett McLain
是的,这就是我正在努力解决的问题,将每个订单内的所有内容求和。 - mtwallet
1
我更新了我的答案,但是看起来@chridam发布了一个相当不错的解决方案! - Brett McLain
2
的确!我需要一点头脑操练和自己解决问题。再次感谢您所有的帮助,非常感激。 - mtwallet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接