Найти индекс первого соответствующего элемента $gte с помощью $indexOfArray

MongoDB имеет $indexOfArray чтобы позволить вам найти индекс массива элемента, например:

$indexOfArray: ["$article.date", ISODate("2019-03-29")]

Можно ли использовать операторы сравнения с $indexOfArray вместе, как:

$indexOfArray: ["$article.date", {$gte: ISODate("2019-03-29")}]

1 ответ

Решение

Не возможно с $indexOfArray поскольку это будет искать только совпадение с выражением в качестве второго аргумента.

Вместо этого вы можете создать такую ​​конструкцию:

db.data.insertOne({
    "_id" : ObjectId("5ca01e301a97dd8b468b3f55"),
    "array" : [
            ISODate("2018-03-01T00:00:00Z"),
            ISODate("2018-03-02T00:00:00Z"),
            ISODate("2018-03-03T00:00:00Z")
    ]
})

db.data.aggregate([
  { "$addFields": {
    "matchedIndex": {
      "$let": {
        "vars": {
          "matched": {
            "$arrayElemAt": [
              { "$filter": {
                "input": {
                  "$zip": {
                    "inputs": [ "$array", { "$range": [ 0, { "$size": "$array" } ] }]
                  }
                },
                "cond": { "$gte": [ { "$arrayElemAt": ["$$this", 0] }, new Date("2018-03-02") ] }
              }},
              0
            ]
          }
        },
        "in": {
          "$arrayElemAt": [{ "$ifNull": [ "$$matched", [0,-1] ] },1]
        }
      }
    }
  }}
])

Который вернется за $gte из Date("2018-03-02"):

{
        "_id" : ObjectId("5ca01e301a97dd8b468b3f55"),
        "array" : [
                ISODate("2018-03-01T00:00:00Z"),
                ISODate("2018-03-02T00:00:00Z"),
                ISODate("2018-03-03T00:00:00Z")
        ],
        "matchedIndex" : 1
}

Или же -1 где условие не было выполнено, чтобы соответствовать $indexOfArray,

Основная предпосылка использует $zip для того, чтобы "спариться" с позициями индекса массива, которые генерируются из $range а также $size массива. Это можно подать на $filter условие, которое вернет ВСЕ соответствующие элементы в указанное условие. Здесь это первый элемент "пары" (являющийся исходным содержимым массива) через $arrayElemAt соответствие указанному условию, используя $gte

{ "$gte": [ { "$arrayElemAt": ["$$this", 0] }, new Date("2018-03-02") ] }

$filterвернет ВСЕ элементы после (в случае $gte) или пустой массив, где ничего не было найдено. В соответствии с $indexOfArray вам нужен только первый матч, который делается с другой упаковкой $arrayElemAt на выходе для 0 позиция.

Поскольку результатом может быть пропущенное значение (что и происходит $arrayElemAt: [[], 0]) тогда вы используете [ $ifNull][8] чтобы проверить результат и передать массив из двух элементов обратно с -1 в качестве второго элемента в случае, когда выход не был определен. В любом случае этот "спаренный" массив имеет второй элемент (индекс 1) снова извлекается через $arrayElemAt чтобы получить первый соответствующий индекс условия.

Конечно, поскольку вы хотите сослаться на это целое выражение, в конце оно просто читается немного чище. $let, но это необязательно, так как вы можете "встроить" в $ifNull если хотел.

Так что это возможно, это просто немного сложнее, чем размещение выражения диапазона внутри $indexOfArray,

Обратите внимание, что любое выражение, которое фактически возвращает единственное значение для совпадения равенства, просто прекрасно. Но так как операторы любят $gte вернуть boolean тогда это не будет равно какому-либо значению в массиве, и, следовательно, вид обработки с $filter и тогда добыча - это то, что вам нужно.

Другие вопросы по тегам