Использование $ mergeObjects в массиве словарей с агрегатом MongoDB - PullRequest
0 голосов
/ 24 октября 2019

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

Это мой ввод:

[
    {
        "missions": {
            "Denver": {
                "savedTowers": 3,
                "savedCity": 0,
                "hoursFasted": 68,
                "fastPointsEarned": 4080
            },
            "Boston": {
                "savedTowers": 2,
                "savedCity": 0,
                "hoursFasted": 32,
                "fastPointsEarned": 1920
            }
        }
    },
    {
        "missions": {
            "Denver": {
                "savedTowers": 4,
                "savedCity": 0,
                "hoursFasted": 87,
                "fastPointsEarned": 5220
            },
            "Boston": {
                "savedTowers": 7,
                "savedCity": 1,
                "hoursFasted": 120,
                "fastPointsEarned": 7200
            }
        }
    }
]

Это код:

db.collection("users").aggregate([
    {
        "$match": {
            "missions": {
                "$exists": true,
                "$gt": {}
            }
        }
    },
    {
        "$project": {
            "_id": 0,
            "city": {
                "$objectToArray": "$missions"
            }
        }
    },
    {
        "$unwind" : "$city"
    },
    {
        "$group": {
            "_id": {
                "city": "$city.k"
            },
            "cities": {
                "$addToSet": "$city.k"
            },
            "stats": {
                "$addToSet": "$city.v"
            },
            "players": {
                "$sum": 1
            }
        }
    },
    {
        "$project": {
            "_id": 0,
            "city": "$_id.city",
            "stats": {
                "$mergeObjects": "$stats"
            },
            "players": "$players"
        }
    }
]).toArray(function(err, response) {
    if (err != null) {
        console.log("Error: " + err.message);
        handleError(res, "Failed to fetch Mission Analytics", err.message);
    } else {
        res.status(200).send({ "mission_stats": response });                
    }
});

Это фактический вывод:

{
    "mission_stats": [
        {
            "city": "Boston",
            "stats": {
                "savedTowers": 2,
                "savedCity": 0,
                "hoursFasted": 32,
                "fastPointsEarned": 1920
            },
            "players": 2
        },
        {
            "city": "Denver",
            "stats": {
                "savedTowers": 3,
                "savedCity": 0,
                "hoursFasted": 68,
                "fastPointsEarned": 4080
            },
            "players": 2
        }
    ]
}

Это ожидаемый вывод:

{
    "mission_stats": [
        {
            "city": "Boston",
            "stats": {
                "savedTowers": 9,
                "savedCity": 0,
                "hoursFasted": 152,
                "fastPointsEarned": 9120
            },
            "players": 2
        },
        {
            "city": "Denver",
            "stats": {
                "savedTowers": 7,
                "savedCity": 0,
                "hoursFasted": 155,
                "fastPointsEarned": 9300
            },
            "players": 2
        }
    ]
}

Как получилось, что $ mergeObjects сократил массив статистики только до одного объекта,но не удалось объединить значения тоже? Я не вижу совокупных значений в конечном объединенном объекте.

Ответы [ 2 ]

2 голосов
/ 24 октября 2019

Вы перезаписываете статистику последней $mergeObjects операцией.

Вы можете попробовать агрегацию ниже (не проверено)

Необходимо преобразовать объект значения в массив пар ключ-значение, а затем $unwind+$group для группировки по каждому ключу и накопления статистики. Последний шаг для возврата к названному объекту значения ключа.

db.colname.aggregate([
  /** match stage **/
  {"$project":{"city":{"$objectToArray":"$missions"}}},
  {"$unwind":"$city"},
  {"$addFields":{"city-v":{"$objectToArray":"$city.v"}}},
  {"$unwind":"$city-v"},
  {"$group":{
    "_id":{"id":"$city.k","key":"$city-v.k"},
    "stats":{"$sum":"$city-v.v"}
  }},
  {"$group":{
    "_id":"$_id.id",
    "players":{"$sum":1},
    "stats":{"$mergeObjects":{"$arrayToObject":[[["$_id.key","$stats"]]]}}
  }}
])
1 голос
/ 24 октября 2019

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

Я полагаю, что лучший подход заключается в суммировании различныхвведите в поле $city.v в первой операции $group, затем используйте вторую операцию $group, чтобы $push вернули итоговую статистику. С помощью последней операции $project для очистки данных.

{
    "$group": {
        "_id": {
            "city": "$city.k"
        },
        "savedTowersTotal": {
            "$sum": "$city.v.savedTowers"
        },
        "savedCityTotal": {
            "$sum": "$city.v.savedCity"
        },
        "hoursFastedTotal": {
            "$sum": "$city.v.hoursFasted"
        },
        "fastPointsEarnedTotal": {
            "$sum": "$city.v.fastPointsEarned"
        },
        "players": {
            "$sum": 1
        }
    }
}, {
    "$group": {
        "_id": {
            "city": "$_id",
            "players": "$players"
        },
        "stats": {
            "$push": {
                "savedTowers": "$savedTowersTotal",
                "savedCity": "$savedCityTotal",
                "hoursFasted": "$hoursFastedTotal",
                "fastPointsEarned": "$fastPointsEarnedTotal"
            }
        }
    }
}, {
    "$project": {
        "_id": 0,
        "city": "$_id.city",
        "stats": 1,
        "players": "$_id.players"
    }
}
...