mongodb - arrayFilters с несколькими идентификаторами и смешанными условиями (И / ИЛИ) - PullRequest
3 голосов
/ 27 марта 2020

Я пытаюсь выполнить запрос mongodb, который соответствует некоторым условиям вложенного массива, без успеха ...

Документы:

[
   {
      "_id":1,
      "name":"foo",
      "games":[
         {
            "name":"Game1",
            "data":[
               { "data_id":1, "date":"YYYMMDD" },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         },
         {
            "name":"Game2",
            "data":[
               { "data_id":1, "date":"YYYMMDD" },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         }
      ]
   },
   {
      "_id":2,
      "name":"bar",
      "games":[
         {
            "name":"Game2",
            "data":[ 
               { "data_id":1, "date":"YYYMMDD" },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         }
      ]
   }
]

И я хочу сделать update_many, сопоставляя следующие данные :

all documents
AND ( (_id = 1 and games.name = 'Game1' and games.data.data_id = 1 ) OR (_id = 2 and games.name = 'Game2' and games.data.data_id = 1 ) )
set valid = True

Итак, для каждого документа я хочу обновить указанный c games.data.data_id элемент.

Что я пробовал

query = {}
update = {"$set": {"games.$[i].data.$[j].valid": True}}
array_filters = [{'$or': [{'_id': 1, 'i.name': 'Game1', 'j.data_id': 1}, {'_id': 2, 'i.name': 'Game2', 'j.data_id': 1}]}]

Но он возвращает ошибку:

WriteError: Error parsing array filter :: caused by :: Expected a single top-level field name, found 'i' and 'j'

Ожидаемый вывод:

[
   {
      "_id":1,
      "name":"foo",
      "games":[
         {
            "name":"Game1",
            "data":[
               { "data_id":1, "date":"YYYMMDD", "valid": True },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         },
         {
            "name":"Game2",
            "data":[
               { "data_id":1, "date":"YYYMMDD" },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         }
      ]
   },
   {
      "_id":2,
      "name":"bar",
      "games":[
         {
            "name":"Game2",
            "data":[ 
               { "data_id":1, "date":"YYYMMDD", "valid": True },
               { "data_id":2, "date":"YYYMMDD" }
            ]
         }
      ]
   }
]

Итак, что я делаю не так? Как это сделать? Я пробовал несколько других вариантов, таких как объединение $ или и $ и, но каждый раз, когда я использую несколько идентификаторов в одном и том же состоянии, он не работает

1 Ответ

3 голосов
/ 31 марта 2020

В соответствии с правилами использования arrayFilters, вы не можете использовать условия по своему усмотрению, например, поле _id в arrayFilters. См. Укажите arrayFilters для операций обновления массива

  • В документе обновления используйте позиционный оператор с фильтром $ [], чтобы определить идентификатор, на который вы затем будете ссылаться в массиве. фильтровать документы. У вас не может быть документа фильтра массива для идентификатора, если идентификатор не включен в документ обновления.
  • Вы можете включать один и тот же идентификатор несколько раз в документ обновления; однако для каждого отдельного идентификатора ($ [identifier]) в документе обновления необходимо указать ровно один соответствующий документ фильтра массива. То есть вы не можете указать несколько документов фильтра массива для одного и того же идентификатора.

Вот другие способы обновления; но они не могут использовать arrayFilters так, как вы хотели.


1. Использование Bulk.find.arrayFilters:

var bulk = db.games.initializeUnorderedBulkOp();
bulk.find( { _id: 1 } )
    .arrayFilters( [ { "g.name": "Game1" }, { "d.data_id": 1 } ] )
    .updateOne( { $set: { "games.$[g].data.$[d].valid": true } } );
bulk.find( { _id: 2 } )
    .arrayFilters( [ { "g.name": "Game2" }, { "d.data_id": 1 } ] )
    .updateOne( { $set: { "games.$[g].data.$[d].valid": true } } );
bulk.execute();

Подробнее: Bulk.find.arrayFilters


2. Обновление с помощью конвейера агрегации

Начиная с MongoDB 4.2, метод db.collection.updateMany() может принимать конвейер агрегации [ <stage1>, <stage2>, ... ], который указывает модификации, которые необходимо выполнить. Подробности: Обновление с агрегационным конвейером

db.games.updateMany(
  {
      $or: [ { $and: [ { _id: 1 }, { "games.name": "Game1" }, { "games.data.data_id": 1 } ] }, 
               { $and: [ { _id: 2 }, { "games.name": "Game2" }, { "games.data.data_id": 1 } ] } 
      ]
  },
  [
    { 
      $set: { 
          games: { 
              $map: { 
                  input: "$games", as: "g",
                  in: { 
                      $mergeObjects: [ 
                          "$$g", 
                          { data: { 
                               $map: { 
                                   input: "$$g.data", as: "d",
                                   in: { 
                                       $cond: [
                                           { $or: [ 
                                               { $and: [ { $eq: [ "$_id", 1 ] }, { $eq: [ "$$g.name", "Game1" ] }, { $eq: [ "$$d.data_id", 1 ] } ] },
                                               { $and: [ { $eq: [ "$_id", 2 ] }, { $eq: [ "$$g.name", "Game2" ] }, { $eq: [ "$$d.data_id", 1 ] } ] }
                                            ] }, 
                                            { $mergeObjects: [ "$$d", { valid: true } ] }, 
                                            "$$d"
                                       ]
                                   }
                               }
                          } }
                      ]
                  }
              }
          }
      }
    } 
  ]
)


3. Конвейер агрегации и обновление

Это агрегация + обновление работает с версиями MongoDB ранее, чем 4.2 .

db.games.aggregate( [
  { 
      $addFields: { 
          games: { 
              $map: { 
                  input: "$games", as: "g",
                  in: { 
                      $mergeObjects: [ 
                          "$$g", 
                          { data: { 
                               $map: { 
                                   input: "$$g.data", as: "d",
                                   in: { 
                                       $cond: [
                                           { $or: [ 
                                               { $and: [ { $eq: [ "$_id", 1 ] }, { $eq: [ "$$g.name", "Game1" ] }, { $eq: [ "$$d.data_id", 1 ] } ] },
                                               { $and: [ { $eq: [ "$_id", 2 ] }, { $eq: [ "$$g.name", "Game2" ] }, { $eq: [ "$$d.data_id", 1 ] } ] }
                                            ] }, 
                                            { $mergeObjects: [ "$$d", { valid: true } ] }, 
                                            "$$d"
                                       ]
                                   }
                               }
                          } }
                      ]
                  }
              }
          }
      }
  }
] ).forEach( doc => db.games.updateOne( { _id: doc._id }, { $set: { games: doc.games } } ) )
...