【问题标题】:MeteorJS: why takes so long in foreach? Should I use MongoDB aggregation operations, how?MeteorJS:为什么在 foreach 中需要这么长时间?我应该使用 MongoDB 聚合操作,如何?
【发布时间】:2017-05-10 00:46:46
【问题描述】:

我有一个集合,其中单个文档是这样的:

{
    _id: 'string'
    date: Date,
    user: 'usera',
    sections: [
        { heading: 'string a', score: 10 },
        { heading: 'string a', score: 104 },
        { heading: 'string b', score: 123 },
        { heading: 'string b', score: 15 },
        { heading: 'string b', score: 7 },
        { heading: 'string c', score: 1 }
    ]
    totalScore: 259
}

该集合有数十万个文档。

我需要按日期从集合中搜索,并给出部分分数的总和以及totalScores 的总和。

如果我这样做,在 Meteor 出版物中

Meteor.publish('mydata', function() {
  var rets = {}

  Mycollections.find({date: {$gte: new Date(2016, 8, 1), $lt: new Date (2016, 12, 24)}}.forEach(function (doc) {
     if (!rets[doc.user]) {
       rets[doc.user] = {

         tot: 0, 
         sections: {}
       }
     }

     rets[doc.user].tot += doc.totalScore;
     for (var i in doc.sections) {
        if (!rets[doc.user].sections[doc.sections[i].heading]) {
          sections[doc.sections[i].heading] = 0;
        }

        rets[doc.user].sections[doc.sections[i].heading] += sections[doc.sections[i].score; 
     }
  });

  return rets;

});

Mycollections.find({date: {$gte: new Date(2016, 8, 1), $lt: new Date (2016, 12, 24)}} 的执行时间不到 0 毫秒。

对于单个 forEach 循环,执行大约需要 0-1 毫秒,如果我获取 20 000 个文档,则需要 20 秒才能发布。

MongoDB 聚合在这方面有何帮助? 或者我如何在服务器端更快地执行计算?

我需要的是集合中每个用户的总分和每个部分的总分。

{
  usera: {
   tot: 235
   sections[
    {'string a': 200},
    {'string b': 35},
    {...}
  ]
 }, 
 userb: {
 ...
 }

}

【问题讨论】:

    标签: javascript mongodb meteor mongodb-query aggregation-framework


    【解决方案1】:

    您可以执行聚合:

    • 符合您的日期范围
    • 为每个用户计算 totalScore
    • 每个部分计算score
    • user 分组并获取包含分数的部分数组

    这可能包括:

    • $match 用于您的日期范围
    • $group 计算 totalScore 并将 $$ROOT 文档推送到新字段以跟踪下一组
    • $unwind$$ROOT文档将数组转换为json对象
    • $unwindsections 数组每条记录一行(准备分组)
    • $groupusersections 将所有部分的得分相加
    • $group 将所有部分的得分重新组合成一条记录,每 user

    查询是:

    db.document.aggregate([{
        $match: {
            date: {
                $gte: ISODate("2016-08-01T00:00:00.0Z"),
                $lte: ISODate("2016-12-24T23:59:00.0Z")
            }
        }
    }, {
        $group: {
            _id: {
                "user": "$user"
            },
            totalScore: { $sum: "$totalScore" },
            document: { $push: "$$ROOT" }
        }
    }, {
        $unwind: "$document"
    }, {
        $unwind: "$document.sections"
    }, {
        $group: {
            _id: {
                "user": "$_id.user",
                "sections": "$document.sections.heading"
            },
            score: { $sum: "$document.sections.score" },
            totalScore: { $first: "$totalScore" }
        }
    }, {
        $group: {
            _id: "$_id.user",
            sections: { $push: { "name": "$_id.sections", "score": "$score" } },
            totalScore: { $first: "$totalScore" }
        }
    }])
    

    它给了你类似的东西:

    { "_id" : "userc", "sections" : [ { "name" : "string c", "score" : 1 }, { "name" : "string b", "score" : 145 }, { "name" : "string a", "score" : 114 } ], "totalScore" : 259 }
    { "_id" : "usera", "sections" : [ { "name" : "string c", "score" : 12 }, { "name" : "string b", "score" : 1873 }, { "name" : "string a", "score" : 230 } ], "totalScore" : 269 }
    

    在 Meteor 中,您可以执行聚合:

    MyCollection.aggregate(
        // aggregation query here
    );
    

    【讨论】:

    • 谢谢!我是 MongoDB 聚合的新手,$$ROOT 部分对我来说是全新的,并且分组了三倍。现在整个查询将在 50 毫秒内运行!圣诞快乐!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多