Соответствует ключу документа, ближайшему к значению поиска - PullRequest
0 голосов
/ 06 марта 2019

У меня есть следующая коллекция:

{
    "_id" : "Stats1",
    "minutes" : {
        "0" : [
            {
                "0" : {
                    "f" : 1,
                    "t" : 0,
                    "v" : "0"
                }
            }
        ],
        "22" : [
            {
                "2" : "1"
            }
        ],
        "29" : [
            {
                "32" : "2"
            }
        ],
        "38" : [
            {
                "40" : "3"
            }
        ]
    }
}

и когда я пытаюсь:

 db.stats.aggregate()
  .project({"_id":"1", "minArray": {"$objectToArray": "$minutes"}})

Я получаю сообщение об ошибке:

"$ objectToArray требует ввода документа, найдено: массив"

и когда я пытаюсь:

 db.stats.aggregate()
  .project({"_id":"1", "minArray": {"$arrayToObject": "$minutes"}})

Я получаю сообщение об ошибке:

"$ arrayToObject требует ввода массива, найдено: объект"

enter image description here

Я хотел бы получить ближайшее значение для точной минуты или меньше 30:

{ "minute" : "29", "value" : [{ "32" : "2"}] }

1 Ответ

1 голос
/ 06 марта 2019

Таким образом, ошибки заключаются в том, что без $match ваш конвейер пытается получить доступ к другим документам, которые не имеют ожидаемой структуры. Это действительно что-то отдельное, чтобы разобраться, хотя.

Чтобы на самом деле ответить на ваш вопрос с его конечной целью, вам нужен такой конвейер:

var _id = "Stats1";
var target = 30;

db.stats.aggregate([
  { "$match": { "_id" : _id } },
  { "$replaceRoot": {
    "newRoot": {
      "$let": {
        "vars": {
          "working": { 
            "$map": {
              "input": { "$objectToArray": "$minutes" },
              "in": {
                "k": { "$toInt": "$$this.k" },
                "v": "$$this.v",
                "diff": { "$abs": { "$subtract": [ target, { "$toInt": "$$this.k" }] } }
              }         
            }
          }
        },
        "in": {
          "$arrayToObject": {
            "$map": {
              "input": {
                "$filter": {
                  "input": {
                    "$objectToArray": {
                      "$arrayElemAt": [
                        "$$working",
                        { "$indexOfArray": [ "$$working.diff", { "$min": "$$working.diff" } ] }
                      ]
                    }
                  },
                  "cond": { "$ne": [ "$$this.k", "diff" ] }
                }
              },
              "in": {
                "k": { "$cond": [{ "$eq": [ "$$this.k", "k"] }, "minute", "value" ] },
                "v": { "$cond": [{ "$eq": [ "$$this.k", "k"] }, { "$toString": "$$this.v" }, "$$this.v" ] }
              }
            }
          }
        }
      }
    }
  }}
])

Что, конечно, возвращает желаемый результат:

{ "minute" : "29", "value" : [ { "32" : "2" } ] }

В последовательности вы делаете $objectToArray, как вы изначально пытались, но затем вам нужно, чтобы значение key или "k" было фактически преобразовано в числовое значение для сравнения. Вам также необходимо вычислить разницу этого значения из значения, которое вы ищете, в данном случае 30. Это дает вам «рабочую» копию данных в виде массива, что важно для следующих этапов ввода.

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

Сначала вы хотите извлечь элемент из этого рабочего массива, где разница (при использовании $abs, так что положительные и отрицательные значения совпадают) - это минимальное значение с $min. Это дает позицию первого совпадения из $indexOfArray и используется с $arrayElemAt, чтобы вернуть этот единственный выбранный элемент из рабочего массива.

Нам не нужны все поля в этом объекте, поэтому $objectToArray преобразует этот отдельный объект в парные объекты "k" и "v", и первый шаг - $filter где ключ - это поле разница и удалите его из этого списка.

Затем вы хотите переименовать поля и изменить некоторые форматы данных, поэтому $map выполняет итерацию оставшегося массива (всего две записи), присваивая читаемые имена и устанавливая формат строки для "minute".

Наконец, это может вернуться к объекту как $arrayToObject в качестве конечного результата. Поскольку мы хотели ссылаться на этот массив "working" несколько раз, мы объявляем в $let, что позволяет нам это делать. И поскольку все это было выражением, которое выводит то, что вы хотите в качестве документа, вы используете $replaceRoot, чтобы обернуть это как «выражение», в основном это единственный ожидаемый аргумент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...