Как использовать $ regex внутри $ или как выражение агрегации - PullRequest
0 голосов
/ 01 июня 2018

У меня есть запрос, который позволяет пользователю фильтровать по некоторому строковому полю, используя формат, который выглядит следующим образом: "Где описание последней проверки - любое из: foo или bar" .Это прекрасно работает со следующим запросом:

db.getCollection('permits').find({
  '$expr': {
    '$let': {
      vars: {
        latestInspection: {
          '$arrayElemAt': ['$inspections', {
            '$indexOfArray': ['$inspections.inspectionDate', {
              '$max': '$inspections.inspectionDate'
            }]
          }]
        }
      },
      in: {
        '$in': ['$$latestInspection.description', ['Fire inspection on property', 'Health inspection']]
      }
    }
  }
})

Я хочу, чтобы пользователь мог использовать подстановочные знаки, которые я превращаю в регулярные выражения: "Где описание последней проверки - любое из: Health inspection или Found a * at the property ".

Регулярное выражение, которое я получаю, в этом не нуждается.Проблема, с которой я сталкиваюсь, по-видимому, оператор агрегирования $in не поддерживает сопоставление с помощью регулярных выражений .Поэтому я подумал, что построю это, используя $or, так как документы не говорят, что я не могу использовать регулярные выражения.Это была моя лучшая попытка:

db.getCollection('permits').find({
  '$expr': {
    '$let': {
      vars: {
        latestInspection: {
          '$arrayElemAt': ['$inspections', {
            '$indexOfArray': ['$inspections.inspectionDate', {
              '$max': '$inspections.inspectionDate'
            }]
          }]
        }
      },
      in: {
        '$or': [{
          '$$latestInspection.description': {
            '$regex': /^Found a .* at the property$/
          }
        }, {
          '$$latestInspection.description': 'Health inspection'
        }]
      }
    }
  }
})

За исключением того, что я получаю сообщение об ошибке:

"Unrecognized expression '$$latestInspection.description'"

Я думаю, что не могу использовать $$latestInspection.description в качестве ключа объекта, но яЯ не уверен (мои знания здесь ограничены), и я не могу найти другой способ сделать то, что я хочу.Итак, вы видите, что я даже не смог пройти достаточно далеко, чтобы посмотреть, смогу ли я использовать $regex в $or.Я ценю всю помощь, которую я могу получить.

1 Ответ

0 голосов
/ 01 июня 2018

Все внутри $expr является выражением агрегации, и документация не может "сказать, что вы не можете явно" , но отсутствие какого-либо именованного оператора и JIRA, выпуск SERVER-11947 , безусловно, говорят это.Поэтому, если вам нужно регулярное выражение, у вас действительно нет другого выбора, кроме как использовать $where вместо:

db.getCollection('permits').find({
  "$where": function() {
    var description = this.inspections
       .sort((a,b) => b.inspectionDate.valueOf() - a.inspectionDate.valueOf())
       .shift().description;

     return /^Found a .* at the property$/.test(description) ||
           description === "Health Inspection";

  }
})

Вы все еще можете использовать $expr ивыражения агрегации для точного соответствия или просто сохраняйте сравнение в пределах $where в любом случае.Но в настоящее время единственное регулярное выражение, которое понимает MongoDB, это $regex внутри выражения "query" .

Если вы действительно "require" выражение конвейерного агрегации, которое не позволяет использовать $where, тогда единственный действующий подход - сначала спроектировать поле отдельно от массива, а затем $matchс помощью регулярного выражения запроса:

db.getCollection('permits').aggregate([
  { "$addFields": {
     "lastDescription": {
       "$arrayElemAt": [
         "$inspections.description",
         { "$indexOfArray": [
           "$inspections.inspectionDate",
           { "$max": "$inspections.inspectionDate" }
         ]}
       ]
     }
  }},
  { "$match": {
    "lastDescription": {
      "$in": [/^Found a .* at the property$/,/Health Inspection/]
    }
  }}
])

Что приводит нас к тому, что вы, похоже, ищете элемент в массиве с максимальным значением даты.Синтаксис JavaScript должен прояснить, что правильный подход - вместо этого $sort массив при «обновлении».Таким образом, «первый» элемент в массиве может быть «последним».И это то, что вы можете сделать с помощью обычного запроса.

Чтобы поддерживать порядок, убедитесь, что новые элементы добавляются в массив с $push и $sort вот так:

db.getCollection('permits').updateOne(
  { "_id": _idOfDocument },
  {
    "$push": {
      "inspections": {
        "$each": [{ /* Detail of inspection object */ }],
        "$sort": { "inspectionDate": -1 }
      }
    }
  }
)

Фактически с пустым аргументом массива $each и updateMany() обновит все ваши существующие документы:

db.getCollection('permits').updateMany(
  { },
  {
    "$push": {
      "inspections": {
        "$each": [],
        "$sort": { "inspectionDate": -1 }
      }
    }
  }
)

Это действительно необходимо только в том случае, если вы фактически «изменяете» дату, сохраненную во время обновлений, и эти обновления лучше всего выпускать с bulkWrite(), чтобы эффективно выполнять «и» обновление, и«сортировка» массива:

db.getCollection('permits').bulkWrite([
  { "updateOne": {
    "filter": { "_id": _idOfDocument, "inspections._id": indentifierForArrayElement },
    "update": {
      "$set": { "inspections.$.inspectionDate": new Date() }
    }
  }},
  { "updateOne": {
    "filter": { "_id": _idOfDocument },
    "update": {
      "$push": { "inspections": { "$each": [], "$sort": { "inspectionDate": -1 } } }
    }
  }}
])

Однако, если вы на самом деле не «изменяли» дату, то, вероятно, имеет смысл просто использовать модификатор $positionи «предварительное ожидание» массива вместо «добавления» и избежание каких-либо издержек $sort:

db.getCollection('permits').updateOne(
  { "_id": _idOfDocument },
  { 
    "$push": { 
      "inspections": {
        "$each": [{ /* Detail of inspection object */ }],
        "$position": 0
      }
    }
  }
)

С массивом, постоянно отсортированным или по крайней мере построенным так«последняя» дата на самом деле всегда является «первой» записью, тогда вы можете просто использовать обычный запрос expression:

db.getCollection('permits').find({
  "inspections.0.description": { 
    "$in": [/^Found a .* at the property$/,/Health Inspection/]
  }
})

Итак, урок здесь - не пытайтесь навязывать вычисленные выражения вашей логике там, где вам действительно не нужно.Не должно быть веских причин, по которым вы не можете заказать содержимое массива как «сохраненное», чтобы иметь "самую последнюю дату first " , и даже если вы думали, что вам нужен массив в любом другомВ таком случае вам, вероятно, следует взвесить, какой вариант использования более важен.

После повторного вывода вы даже можете в некоторой степени воспользоваться индексом, если регулярные выражения привязаны к началу строки или, по крайней мере,что-то еще в выражении запроса делает точное совпадение.

Если вы чувствуете, что действительно не можете переупорядочить массив, тогда запрос $where - ваш единственный вариант до появления проблемы JIRA.решает.К счастью, это действительно актуальная версия 4.1, но в лучшем случае это, скорее всего, от 6 месяцев до года.

...