Вычисление различных рейтингов выбранных пользователей в одном запросе mongodb - PullRequest
0 голосов
/ 06 января 2020

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

Ниже приведен пример пользовательского документа:

{
    "_id": "5e0a361d1ca215003e79f388",
    "score_day": 20,
    "score_week": 203,
    "score_month": 850
}

Ожидаемый результат при массиве идентификаторов пользователей:

[
    {
        "_id": "???",
        "rank_day": 42,
        "rank_month": 84,
        "rank_year": 65
    },
    {
        "_id": "???",
        "rank_day": 12,
        "rank_month": 8,
        "rank_year": 68
    },
    ...
]

Ответы [ 2 ]

0 голосов
/ 08 января 2020

Как я понял из вашего вопроса, вы хотите дать оценку на основе баллов. Пока еще нет прямого оператора для получения ранга документов MongoDB. Но есть обходной путь, который будет хорошо работать для тех коллекций, где значение в поле, для которого требуется рассчитать ранг, не одинаково для более чем 2 документов или если можно иметь разные ранги для одного и того же значения, кратного документы.

[ 
    { $sort: { score_day: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_day" } }, 
    { $addFields: { "data.rank_day": { $add: [ "$rank_day", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } }, 
    { $sort: { score_month: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_month" } }, 
    { $addFields: { "data.rank_month": { $add: [ "$rank_month", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } }, 
    { $sort: { score_year: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_year" } }, 
    { $addFields: { "data.rank_year": { $add: [ "$rank_year", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } } 
]

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

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

[ 
    { $addFields: { overAllScore: { $add: [ "$score_day", "$score_month", "$score_year" ] } } }, 
    { $sort: { score_day: -1, overAllScore: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_day" } }, 
    { $addFields: { "data.rank_day": { $add: [ "$rank_day", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } }, 
    { $sort: { score_month: -1, overAllScore: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_month" } }, 
    { $addFields: { "data.rank_month": { $add: [ "$rank_month", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } }, 
    { $sort: { score_year: -1, overAllScore: -1 } }, 
    { $group: { _id: null, data: { $push: "$$ROOT" } } }, 
    { $unwind: { path: "$data", includeArrayIndex: "rank_year" } }, 
    { $addFields: { "data.rank_year": { $add: [ "$rank_year", 1 ] } } }, 
    { $replaceRoot: { newRoot: "$data" } }
]

Наконец, вы можете добавить этап проекта, чтобы исключить поля оценки, чтобы показать только необходимые данные.

0 голосов
/ 07 января 2020

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

Возможно, вы захотите взглянуть на отсортированные реализации базы данных, такие как отсортированные наборы Redis, если это окажется слишком медленным. Пн go не был создан для этого варианта использования.

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

var targetIds = ["5e0a361d1ca215003e79f388", "5e0a361d1ca215003e79f389", ...];

db.collection.aggregate([
    { // handle multiple pipelines at once on the same documents
        $facet: {
            rank_day: [
                { // we only need the `_id` and `score_XXX` field, this will make the query use less memory
                    $project: {
                        _id: 1,
                        score_day: 1
                    }
                },
                { // sort on the score
                    $sort: {
                        score_day: -1
                    }
                },
                { // push all documents, sorted, into an array
                    $group: {
                        _id: "",
                        rankings: {
                            $push: "$$ROOT"
                        }
                    }
                },
                { // unwind the formed array back into separate documents, but pass the index
                    $unwind: {
                        path: "$rankings",
                        includeArrayIndex: "rank"
                    }
                },
                { // find the documents we need
                    $match: {
                        "rankings._id": { "$in": targetIds }
                    }
                },
                { // we only need the _id and the rank, score is useless now
                    $project: {
                        _id: "$rankings._id",
                        rank: 1
                    }
                }
            ],
            // handle the 2 other rankings in same way
            rank_week: [
                {
                    $project: {
                        _id: 1,
                        score_week: 1
                    }
                },
                {
                    $sort: {
                        score_week: -1
                    }
                },
                {
                    $group: {
                        _id: "",
                        rankings: {
                            $push: "$$ROOT"
                        }
                    }
                },
                {
                    $unwind: {
                        path: "$rankings",
                        includeArrayIndex: "rank"
                    }
                },
                {
                    $match: {
                        "rankings._id": { "$in": targetIds }
                    }
                },
                {
                    $project: {
                        _id: "$rankings._id",
                        rank: 1
                    }
                }
            ],
            rank_month: [
                {
                    $project: {
                        _id: 1,
                        score_month: 1
                    }
                },
                {
                    $sort: {
                        score_month: -1
                    }
                },
                {
                    $group: {
                        _id: "",
                        rankings: {
                            $push: "$$ROOT"
                        }
                    }
                },
                {
                    $unwind: {
                        path: "$rankings",
                        includeArrayIndex: "rank"
                    }
                },
                {
                    $match: {
                        "rankings._id": { "$in": targetIds }
                    }
                },
                {
                    $project: {
                        _id: "$rankings._id",
                        rank: 1
                    }
                }
            ]
        }
    },


    // cleanup result to expected output
    { // add the searched _ids and unwind them so we can project and filter the arrays in each document
        $addFields: {
            _id: targetIds
        }
    },
    {
        $unwind: {
            path: "$_id"
        }
    },
    { // filter the rankings based on the _id
        $project: {
            _id: 1,
            rank_day: { $filter: { input: "$rank_day", as: "rank", cond: { $eq: ["$$rank._id", "$_id"] } } },
            rank_week: { $filter: { input: "$rank_week", as: "rank", cond: { $eq: ["$$rank._id", "$_id"] } } },
            rank_month: { $filter: { input: "$rank_month", as: "rank", cond: { $eq: ["$$rank._id", "$_id"] } } }
        }
    },
    { // cleanup the array result to the internal "rank" value
        $project: {
            _id: 1,
            rank_day: { $arrayElemAt: ["$rank_day.rank", 0] },
            rank_week: { $arrayElemAt: ["$rank_week.rank", 0] },
            rank_month: { $arrayElemAt: ["$rank_month.rank", 0] }
        }
    }
]);
...