Подтвердить что ты не робот

Несколько групповых операций с использованием механизма агрегации Mongo

Учитывая набор вопросов, связанных с опросом и идентификатором категории:

> db.questions.find().toArray();
[
    {
        "_id" : ObjectId("4fda05bc322b1c95b531ac25"),
        "id" : 1,
        "name" : "Question 1",
        "category_id" : 1,
        "survey_id" : 1,
        "score" : 5
    },
    {
        "_id" : ObjectId("4fda05cb322b1c95b531ac26"),
        "id" : 2,
        "name" : "Question 2",
        "category_id" : 1,
        "survey_id" : 1,
        "score" : 3
    },
    {
        "_id" : ObjectId("4fda05d9322b1c95b531ac27"),
        "id" : 3,
        "name" : "Question 3",
        "category_id" : 2,
        "survey_id" : 1,
        "score" : 4
    },
    {
        "_id" : ObjectId("4fda4287322b1c95b531ac28"),
        "id" : 4,
        "name" : "Question 4",
        "category_id" : 2,
        "survey_id" : 1,
        "score" : 7
    }
]

Я могу найти среднее значение категории:

db.questions.aggregate(
    { $group : {
        _id : "$category_id",
        avg_score : { $avg : "$score" }
    }
}
);

{
    "result" : [
        {
            "_id" : 1,
            "avg_score" : 4
        },
        {
            "_id" : 2,
            "avg_score" : 5.5
        }
    ],
    "ok" : 1
}

Как получить средние значения средних категорий (обратите внимание, что это отличается от простого усреднения всех вопросов)? Я бы предположил, что выполняю несколько групповых операций, но это не удается:

> db.questions.aggregate(
...   { $group : {
...     _id : "$category_id",
...     avg_score : { $avg : "$score" },
...   }},
...   { $group : {
...     _id : "$survey_id",
...     avg_score : { $avg : "$score" },
...   }}
... );
{
    "errmsg" : "exception: the _id field for a group must not be undefined",
    "code" : 15956,
    "ok" : 0
}
>
4b9b3361

Ответ 1

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

В вашем примере ваш первый запрос создает конвейер документов, которые выглядят следующим образом:

{
    "_id" : 2,
    "avg_score" : 5.5
},
{
    "_id" : 1,
    "avg_score" : 4
}

Это означает, что во втором элементе pipline видна серия документов, в которой единственными ключами являются "_id" и "avg_score". Клавиши "category_id" и "score" больше не существуют в этом потоке документа.

Если вы хотите продолжить агрегирование в этом потоке, вам придется агрегировать, используя ключи, которые видны на этом этапе в конвейере. Поскольку вы хотите усреднить средние значения, вам нужно указать одно постоянное значение для поля _id, чтобы все входные документы сгруппировались в один результат.

Следующий код дает правильный результат:

db.questions.aggregate(
    { $group : {
        _id : "$category_id",
        avg_score : { $avg : "$score" },
        }
    },
    { $group : {
        _id : "all",
        avg_score : { $avg : "$avg_score" },
        }
    }
);

При запуске он производит следующий вывод:

 {
    "result" : [
        {
        "_id" : "all",
        "avg_score" : 4.75
        }
    ],
    "ok" : 1
 }