Агрегация Монго — Сортировка с использованием значения поля из предыдущего конвейера в качестве поля сортировки

#mongodb #sorting #aggregation-framework #grouping

Вопрос:

Я произвел вывод ниже, используя агрегацию mongodb (включая конвейер $group внутри levelsCount поля). :

 {
    "_id" : "1",
    "name" : "First",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 1 },
        { "_id" : "level_Three", "levelNum" : 3, "count" : 1 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 8 }
    ]
}
{
    "_id" : "2",
    "name" : "Second",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 5 },
        { "_id" : "level_Two", "levelNum" : 2, "count" : 2 },
        { "_id" : "level_Three", "levelNum" : 3, "count" : 1 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 3 }
    ]
}
{
    "_id" : "3",
    "name" : "Third",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 1 },
        { "_id" : "level_Two", "levelNum" : 2, "count" : 3 },
        { "_id" : "level_Three", "levelNum" : 3, "count" : 2 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 3 }
    ]
}
 

Теперь мне нужно отсортировать эти документы на основе полей levelNum и count levelsCount элементов массива. Т. е. если у обоих двух документов было количество 5 levelNum: 1 (level_One) , то сортировка идет для сравнения количества levelNum: 2 (level_Two) полей и так далее.

Я вижу, как конвейер $sort будет работать с несколькими полями (что-то вроде { $sort : { level_One : 1, level_Two: 1 } } ), но проблема в том, как получить доступ к этим значениям levelNum каждого элемента массива и установить это значение в качестве имени поля для выполнения сортировки. (Я не мог справиться с этим даже после того, как $размотал levelsCount массив).

P. s: Начальный порядок элементов levelsCount массива может отличаться в каждом документе и не имеет значения.

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

 // Sorted result:
{
    "_id" : "2",
    "name" : "Second",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 5 }, // "level_One's count: 5"  is greater than "level_One's count: 1" in two other documents, regardless of other level_* fields. Therefore this whole document with "name: Second" is ordered first.
        { "_id" : "level_Two", "levelNum" : 2, "count" : 2 },
        { "_id" : "level_Three", "levelNum" : 3, "count" : 1 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 3 }
    ]
}
{
    "_id" : "3",
    "name" : "Third",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 1 },
        { "_id" : "level_Two", "levelNum" : 2, "count" : 3 }, // "level_Two's count" in this document exists with value (3) while the "level_Two" doesn't exist in the below document which mean (0) value for count. So this document with "name: Third" is ordered higher than the below document.
        { "_id" : "level_Three", "levelNum" : 3, "count" : 2 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 3 }
    ]
}
{
    "_id" : "1",
    "name" : "First",
    "levelsCount" : [
        { "_id" : "level_One", "levelNum" : 1, "count" : 1 },
        { "_id" : "level_Three", "levelNum" : 3, "count" : 1 },
        { "_id" : "level_Four", "levelNum" : 4, "count" : 8 }
    ]
}
 

Конечно, я бы предпочел иметь выходной документ в приведенном ниже формате, но первая проблема заключается в сортировке всех документов:

 {
    "_id" : "1",
    "name" : "First",
    "levelsCount" : [
        { "level_One" : 1 },
        { "level_Three" : 1 },
        { "level_Four" : 8 }
    ]
}
 

Комментарии:

1. Предоставьте, пожалуйста, ожидаемый результат в формате json

2. @Valijon К вопросу добавляется ожидаемый результат. Спасибо за продолжение

Ответ №1:

Вы можете сортировать levelNum как по убыванию, так и count по возрастанию,

 db.collection.aggregate([
  {
    $sort: {
      "levelsCount.levelNum": -1,
      "levelsCount.count": 1
    }
  }
])
 

Игровая площадка


Для результата массива в формате ключ-значение levelsCount ,

  • $map для повторения цикла levelsCount массива
  • подготовьте массив пар ключ-значение и преобразуйте его в объект с помощью $arrayToObject
   {
    $addFields: {
      levelsCount: {
        $map: {
          input: "$levelsCount",
          in: {
            $arrayToObject: [
              [{ k: "$this._id", v: "$this.levelNum" }]
            ]
          }
        }
      }
    }
  }
 

Игровая площадка

Комментарии:

1. Большое спасибо.Я столкнулся с 2 проблемами во время тестов: 1. Если в levelsCount массиве в документе нет документа со значением level_Two для levelsCount._id поля; сортировка упорядочит этот документ выше, чем тот, который имеет фактическое level_Two значение для него levelsCount._id . В то время как мне нужно, чтобы отсутствующий документ рассматривался как 0 значение для его count поля, что приведет к более низкому месту в порядке. 2. Значения levelsCount._id на самом деле являются строками, их алфавитная сортировка будет концептуально неверной. Поэтому мне нужно было добавить дополнительное поле с соответствующим номером

2. Я отредактировал входные документы в вопросе

3. я не думаю, что это возможно, возможно, потребуется $unwind деконструировать массив, но определенно это изменит порядок фактических элементов массива levelsCount .

4. Начальный порядок элементов в levelsCount списке на самом деле не имеет значения

5. обновлено, я не понимаю, почему подсчет уровней. levelNum убывает , потому что он упорядочивается на большее число, когда мы направляем сортировку по полю, которое находится в массиве объекта.