【问题标题】:Mongodb: How can I find the nested group that maximizes the values max/min length and calculate the average?Mongodb:如何找到使最大/最小长度值最大化并计算平均值的嵌套组?
【发布时间】:2020-06-01 08:29:29
【问题描述】:

我有一个包含训练 (T) 的集合,其中包含一个练习数组,我想找到使值最大/最小长度最大化并计算平均值的嵌套组。集合如:

[{
    "_id" : ObjectId("5e456e6b33fef4299aa75a7e"),
    "title" : "Training aaa and bbb",
    "exercises" : [{
            "title" : "aaa exercise",
            "goals" : ["aaa"],
            "length" : 10
     },{
            "title" : "bbb exercise",
            "goals" : ["bbb"],
            "length" : 5
     }],
    "createdBy" : "dummy"
},{
    "_id" : ObjectId("5e456e7f33fef4299aa75a7f"),
    "title" : "Training aaa, ccc",
    "exercises" : [{
            "title" : "aaa exercise",
            "goals" : ["aaa"],
            "length" : 5
        },{
            "title" : "aaa exercise",
            "goals" : ["aaa"],
            "length" : 10
        },{
            "title" : "ccc exercise",
            "goals" : ["ccc"],
            "length" : 5
    }],
    "createdBy" : "dummy"
},{
    "_id" : ObjectId("5e49b282e0a271e9f57648ff"),
    "title" : "Training aaa 2",
    "exercises" : [{
            "title" : "aaa",
            "goals" : ["aaa"],
            "length" : 5
    },{
            "title" : "ccc exercise",
            "goals" : ["ccc"],
            "length" : 10
    }],
    "createdBy" : "dummy"
}]

我想通过目标和训练找到最小值/最大值/平均值。对于之前的值,预期值应该符合:

[{
    _id: "aaa"
    min: 5,  // T1: 5  
    max: 15, // T2: 5 + 10
    avg: 10  // T1,T2,T3: (10+15+5)/3 = 10
},{
    _id: "bbb",
    min: 5,  // T1: 5
    max: 5,  // T1: 5
    avg: 5   // T1: 5/1 = 5
},{
    _id: "ccc",
    min: 5,  // T2: 5
    max: 10, // T3: 10
    avg: 5   // T2,T3: (5+10)/2 = 7,5 
}]

在本例中,aaa 在第一次训练中训练 10 分钟,在第二次训练中训练 15 分钟,在第三次训练中训练 5 分钟。所以 min=5, max=15, avg: (10+15+5)/3 = 10

我尝试了以下方法,但虽然很接近,但我没有得到预期的结果:

db.getCollection('trainings').aggregate([
    {$match : {"createdBy" : "dummy" } },
    {$unwind: "$exercises"},
    {$unwind: "$exercises.goals" },
    {$group: {
        _id: "$exercises.goals",
        count: { $sum: 1 },
        lengthAvg: {$avg: "$exercises.length"},
        lengthMin: {$min: "$exercises.length"},
        lengthMax: {$max: "$exercises.length"},
        lengthSum: {$sum: "$exercises.length"}
        }
    }
])

我认为问题在于 $unwind 阶段,它解构了练习和分组训练丢失了。但我不确定如何更改它。

【问题讨论】:

  • 嗯,你确定你的预期结果吗?最少 10 个,最多 15 个,平均 10 个???
  • 我想是的。为什么?你有没有发现什么奇怪的东西?这个想法是计算所有训练的每个目标的最小值、最大值和平均值,并最大化结果。
  • [{ _id: "aaa" min: 5, max: 15, avg: 10 },{ _id: "bbb", min: 5, max: 5, avg: 5 },{ _id :“ccc”,最小:5,最大:10,平均:7.5 }]
  • @mattPen 你是对的。我只关注aaa。我刚刚纠正了它。谢谢。
  • 好的,更好理解。您的目标数组中可以有多个值,即目标:[“aaa”,“ccc”]?

标签: mongodb aggregation-framework


【解决方案1】:

您正在通过查询触及解决方案。诀窍是首先按训练分组以获得每次训练中的目标总和,然后按目标分组以获得所需的指标。

db.collection.aggregate([
  {
    $match: {
      "createdBy": "dummy"
    }
  },
  {
    $unwind: "$exercises"
  },
  {
    $unwind: "$exercises.goals"
  },
  {
    $group: {
      _id: {
        trainingId: "$_id",
        goal: "$exercises.goals",

      },
      totalPerTraining: {
        $sum: "$exercises.length"
      }
    }
  },
 {
    $group: {
      _id: "$_id.goal",
      lengthMin: {
        $min: "$totalPerTraining"
      },
      lengthMax: {
        $max: "$totalPerTraining"
      },
      lengthAvg: {
        $avg: "$totalPerTraining"
      },
      count: {
        $sum: 1
      },
      lengthSum: {
        $sum: "$totalPerTraining"
      }
    }
  }
])

你可以测试一下here

---编辑---

虽然前面的聚合可以完美运行,但是两次展开会消耗大量资源。对于这种需求,我强烈建议使用map/reduce approach,在您的情况下效率更高。

  map = function () {
    var trainingSums = {};
    this.exercises.forEach(function (exercise) {
      exercise.goals.forEach(function (goal) {
        if (trainingSums[goal] == null) {
          trainingSums[goal] = 0;
        }
        trainingSums[goal] += exercise.length;
      })
    });

    for (property in trainingSums) {
      print(trainingSums);
      emit(property, trainingSums[property]);
    }
  };
  reduce = function (key, values) {
    var reducedValues = {};
    reducedValues.sum = values.reduce((a, b) => a + b, 0);
    reducedValues.min = Math.min(...values);
    reducedValues.max = Math.max(...values);
    reducedValues.avg = values.reduce((a, b) => a + b, 0) / values.length;
    reducedValues.count = values.length;
    return reducedValues;
  };
  finalize = function (key, reducedValue) {
    var finalValue = {};
    if (!isObject(reducedValue)) {
      finalValue.sum = reducedValue;
      finalValue.min = reducedValue;
      finalValue.max = reducedValue;
      finalValue.avg = reducedValue;
      finalValue.count = 1;
    } else
      finalValue = reducedValue;
    return finalValue;
  };

map 函数计算训练中每个目标的总和,然后发出。

reduce 函数正在计算您的指标。

finalize 函数用于定义在所有训练中仅找到一次目标时的指标(例如示例中的“ccc”目标),因为在这种情况下不会应用 reduce 函数。

对于具有多个值的键,MongoDB 会应用 reduce 阶段,该阶段收集并压缩聚合数据。

【讨论】:

  • lengthSum: { $sum: "$exercises.length" } 应替换为:lengthSum: { $sum: "$totalPerTraining" }。其他一切都很完美。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-20
  • 2016-08-24
  • 1970-01-01
相关资源
最近更新 更多