Mongodb: Как я могу найти вложенную группу, которая максимизирует значения max / min длины и рассчитать среднее? - PullRequest
0 голосов
/ 17 февраля 2020

У меня есть коллекция с тренировками (T), которая содержит массив упражнений, и я хотел бы найти вложенную группу, которая максимизирует значения max / min длины и вычислить среднее. Коллекция такая:

[{
    "_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, которая разбирает упражнения, а группа по тренировке теряется. Но я не уверен, как это изменить.

1 Ответ

1 голос
/ 18 февраля 2020

Вы касались решения своим запросом. Хитрость заключается в том, чтобы сначала сгруппировать с помощью обучения, чтобы получить сумму целей внутри каждого тренинга, а затем сгруппировать по целям, чтобы получить необходимые показатели.

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"
      }
    }
  }
])

Вы можете проверить это здесь

--- EDIT ---

Несмотря на то, что предыдущая агрегация будет отлично работать, она может потреблять много ресурсов при двойном раскручивании. Я настоятельно рекомендую для такого рода потребностей использовать map / Reduction , более эффективный в вашем случае.

  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 вычисляет сумму для каждой цели в тренировка, затем сгенерируйте это.

Функция приведения вычисляет ваши метрики.

Функция финализации здесь для определения метрик, когда цель была найдена только один раз за все трассы (как ваш 'ccc 'цель в примере), потому что в этом случае функция уменьшения не будет применяться.

Для тех ключей, которые имеют несколько значений , MongoDB применяет фазу уменьшения, которая собирает и уплотняет агрегированные данные.

...