Использовать значения массива в качестве ключа в $match stage mongodb

Учитывая перечень документов, таких как:

[
  {
    _id: 1,
    field1: "test",
    field2: "value2",
    fields: [
      "field1",
      "field2"
    ]
  },
  {
    _id: 2,
    field1: "value1",
    field2: "test",
    fields: [
      "field1",
      "field2"
    ]
  },
  {
    _id: 3,
    field1: "test",
    field2: "value2",
    fields: [
      "field2"
    ]
  },
  {
    _id: 4,
    field1: "value1",
    field2: "value2",
    fields: [
      "field1"
    ]
  },
  {
    _id: 5,
    field1: "test",
    field2: "test",
    fields: []
  }
]

Я хочу получить все документы, которые имеют любое из указанных полей в массиве со значением «тест». Например, в документе с _id: 1field2 является значением массива fields, а field2 имеет test в качестве значения. Напротив, документ с _id: 5 имеет ключи field1 и field2 с проверочным значением, но ни одно из этих полей не присутствует в массиве полей. Итак, учитывая предыдущий список документов, возвращать следует только документы с _id: 1 и _id: 2.

🤔 А знаете ли вы, что...
MongoDB обеспечивает горизонтальное масштабирование, что позволяет легко увеличивать производительность...


1
50
1

Ответ:

Решено

возможный подход в виде шагов

  1. Создайте временное поле массива пар ключ-значение из field1,field2.., чтобы его можно было фильтровать/искать.
  2. Выполните сравнение, используя совпадение.
  3. Удалите временное поле, добавленное на первом этапе.

Подход с $anyElementTrue на стадии матча - демо

db.collection.aggregate([
  {
    $addFields: {
      keyValuePairArray: {
        $filter: {
          input: { $objectToArray: "$$ROOT" },
          cond: { $not: { $in: [ "$$this.k", ["_id", "fields"] ] } }
        }
      }
    }
  },
  {
    $match: {
      $expr: {
        $anyElementTrue: {
          $map: {
            input: "$keyValuePairArray",
            in: {
              $and: [
                { $in: [ "$$this.k", "$fields" ] },
                { $eq: [ "$$this.v", "test" ] }
              ]
            }
          }
        }
      }
    }
  },
  { $unset: "keyValuePairArray" }
])

Другой подход с $filter на этапе матча, проверив его размер - демо

db.collection.aggregate([
  {
    $addFields: {
      keyValuePairArray: {
        $filter: {
          input: { $objectToArray: "$$ROOT" },
          cond: { $not: { $in: [ "$$this.k", ["_id", "fields"] ] } }
        }
      }
    }
  },
  {
    $match: {
      $expr: {
        $gt: [
          {
            $size: {
              $filter: {
                input: "$keyValuePairArray",
                cond: {
                  $and: [
                    { $in: [ "$$this.k", "$fields" ] },
                    { $eq: [ "$$this.v", "test" ] }
                  ]
                }
              }
            }
          },
          0
        ]
      }
    }
  },
  { $unset: "keyValuePairArray" }
])

РЕДАКТИРОВАНИЕ - вам может даже не понадобиться временное поле, удалив _id и fields, поскольку они все равно не повлияют на сравнение $match - проверьте демо

db.collection.aggregate([
  {
    $match: {
      $expr: {
        $anyElementTrue: {
          $map: {
            input: { $objectToArray: "$$ROOT" },
            in: {
              $and: [
                { $in: [ "$$this.k", "$fields" ] },
                { $eq: [ "$$this.v", "test" ] }
              ]
            }
          }
        }
      }
    }
  }
])