BoShurik
@BoShurik
Symfony developer

Как рассчитать значение поля на основании предыдущих документов?

Всем привет!

Имеет место быть следующая коллекция:

---- -------- -------- --------
  id   name     active   rating
 ---- -------- -------- --------
  0    User 0   0        8740
  1    User 1   1        7900
  2    User 2   1        3230
  3    User 3   1        9790
  4    User 4   1        3230
  5    User 5   1        1540
 ---- -------- -------- --------


Необходимо назначить места на основании рейтинга и флага активности:

---- -------- -------- -------- ----------
  id   name     active   rating   position
 ---- -------- -------- -------- ----------
  3    User 3   1        9790     1
  0    User 0   0        8740     2
  1    User 1   1        7900     2
  2    User 2   1        3230     3
  4    User 4   1        3230     3
  5    User 5   1        1540     5
 ---- -------- -------- -------- ----------


Сейчас эта таблица генерируется с помощью кода:

$activePosition = 1;
$overallPosition = 1;

$prevActiveItem = null;
$prevInactiveItem = null;
foreach ($items as &$item) {
    if ($item['active']) {
        $position = &$activePosition;
        $prevItem = $prevActiveItem;
        $prevActiveItem = $item;

        $overallPosition++;
    } else {
        $position = &$overallPosition;
        $prevItem = $prevInactiveItem;
        $prevInactiveItem = $item;
    }

    if ($prevItem && $prevItem['rating'] == $item['rating']) {
        $item['position'] = $prevItem['position'];
    } else {
        $item['position'] = $position;
    }

    $position++;
}

unset($item);


Интересует, можно ли расставить позиции с помощью aggregation framework?
  • Вопрос задан
  • 64 просмотра
Решения вопроса 1
@Urukhayy
В Mongo Aggregation можно сделать через временный массив.

1. Сортируем все документы в порядке убывания rating и active (позиция будет расти сверху вниз)
2. $push всех отсортированных документов во временный массив data
3. Создание нового массива arr и внесение в него всех элементов массива data в том же порядке, но добавляя поле position, которое формируется исходя из положения объекта в массиве data ($indexOfArray)
4. Разбиение ($unwind) массива arr на обычный список документов, но уже отсортированный и с проставленными position
5. Преобразование документов после разбиения к нужному виду

db.getCollection('testo').aggregate([
    { $sort: { rating: -1, active: -1 } },
    { $group: { _id: null, data: { $push: "$$ROOT" } } },
    {
        $project: {
            arr: {
                $map: {
                    input: "$data", as: "e", in: {
                        rating: "$$e.rating", 
                        active: "$$e.active", 
                        position: {
                            $add: [{ $indexOfArray: ["$data", "$$e"] }, 1]
                        }
                    }
                }
            }
        }
    },
    { $unwind: "$arr" },
    { $project: { 
        _id: 0,
        rating: "$arr.rating", 
        active: "$arr.active", 
        position: "$arr.position" } }
])


Если версия MongoDB довольно старая, то придется вручную вписывать все поля, которые нужно учитывать в массиве. Если же версия новая, можно дополнить действием $mergeObjects. Также есть вариант использовать более сложные схемы ($arrayToObject, $objectToArray) для сохранения полей при переходе массива из одного состояния в другое
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы