Использование $ elemMatch и $ or для реализации резервной логики (в проекции) - PullRequest
1 голос
/ 08 марта 2019
db.projects.findOne({"_id": "5CmYdmu2Aanva3ZAy"},
{
  "responses": {
    "$elemMatch": {
      "match.nlu": {
        "$elemMatch": {
          "intent": "intent1",
          "$and": [
            {
              "$or": [
                {
                  "entities.entity": "entity1",
                  "entities.value": "value1"
                },
                {
                  "entities.entity": "entity1",
                  "entities.value": {
                    "$exists": false
                  }
                }
              ]
            }
          ],
          "entities.1": {
            "$exists": false
          }
        }
      }
    }
  }
})

В данном project мне нужен прогноз, содержащий только один ответ, следовательно, $elemMatch. В идеале ищите точное совпадение:

{
    "entities.entity": "entity1",
    "entities.value": "value1"
}

Но если такого совпадения не существует, найдите запись, в которой entities.value не существует

Приведенный выше запрос не работает, потому что, если он найдет элемент с entities.value не установленным, он вернет его. Как я могу получить эту запасную логику в запросе Монго

Вот пример документа

{
    "_id": "5CmYdmu2Aanva3ZAy",
    "responses": [  
      {
        "match": {
          "nlu": [
            {
              "entities": [],
              "intent": "intent1"
            }
          ]
        },
        "key": "utter_intent1_p3vE6O_XsT"
      },
      {
        "match": {
          "nlu": [
            {
              "entities": [{
                  "entity": "entity1",
                  "value": "value1"
                }],
              "intent": "intent1"
            }
          ]
        },
        "key": "utter_intent1_p3vE6O_XsT"
      },
      {
        "match": {
          "nlu": [
            {
              "intent": "intent2",
              "entities": []
            },
            {
              "intent": "intent1",
              "entities": [
                {
                  "entity": "entity1"
                }
              ]
            }
          ]
        },
        "key": "utter_intent2_Laag5aDZv2"
      }
    ]
}

1 Ответ

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

Чтобы ответить на вопрос, первое, с чего нужно начать, это то, что выполнение того, что вы хотите, не так просто, как проекция $elemMatch, и требует специальной логики проекции структуры агрегации. Второй основной принцип: "вложенные массивы - это очень плохая идея " , и именно поэтому:

db.collection.aggregate([
  { "$match": {  "_id": "5CmYdmu2Aanva3ZAy"  } },
  { "$addFields": {
    "responses": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$responses",
            "in": {
              "match": {
                "nlu": {
                  "$filter": {
                    "input": {
                      "$map": {
                        "input": "$$this.match.nlu",
                        "in": {
                          "entities": {
                            "$let": {
                              "vars": {
                                "entities": {
                                  "$filter": {
                                    "input": "$$this.entities",
                                    "cond": {
                                      "$and": [
                                        { "$eq": [ "$$this.entity", "entity1" ] },
                                        { "$or": [
                                          { "$eq": [ "$$this.value", "value1" ] },
                                          { "$ifNull": [ "$$this.value", false ] }
                                        ]}
                                      ]
                                    }
                                  }
                                }
                              },
                              "in": {
                                "$cond": {
                                  "if": { "$gt": [{ "$size": "$$entities" }, 1] },
                                  "then": {
                                    "$slice": [
                                      { "$filter": {
                                        "input": "$$entities",
                                        "cond": { "$eq": [ "$$this.value", "value1" ] }
                                      }},
                                      0
                                    ]
                                  },
                                  "else": "$$entities"
                                }
                              }
                            }
                          },
                          "intent": "$$this.intent"
                        }
                      }
                    },
                    "cond": { "$ne": [ "$$this.entities", [] ] }
                  }
                }
              },
              "key": "$$this.key"
            }
          }
        },
        "cond": { "$ne": [ "$$this.match.nlu", [] ] }
      }
    }
  }}
])

Вернет:

{
  "_id" : "5CmYdmu2Aanva3ZAy",
  "responses" : [
    {
      "match" : {
        "nlu" : [
          {
            "entities" : [
              {
                      "entity" : "entity1",
                      "value" : "value1"
              }
            ],
            "intent" : "intent1"
          }
        ]
      },
      "key" : "utter_intent1_p3vE6O_XsT"
    }
  ]
}

Это извлечение (насколько я могу определить вашу спецификацию), первый соответствующий элемент из вложенного внутреннего массива из entities, где условия для entity и value встречаются ИЛИ , где свойство value не существует.

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

Для запроса глубоко вложенных массивов требуется цепное использование $map и $filter, чтобы обойти содержимое этого массива и вернуть только те элементы, которые соответствуют условиям. Вы не можете указать эти условия в проекции $elemMatch, и даже до недавних выпусков MongoDB это было невозможно даже для атомного обновления таких структур без перезаписи значительных частей документа или введения проблемы с параллелизмом обновлений.

Более подробное объяснение этого приведено в моем существующем ответе на Обновление вложенного массива с MongoDB и со стороны запроса на Поиск в двойном вложенном массиве MongoDB .

Обратите внимание, что оба ответа показывают использование $elemMatch в качестве оператора "запроса", что на самом деле составляет всего лишь "выбор документа" (поэтому не применяется к _id соответствует условию) и не может использоваться совместно с прежним вариантом «проецирования» или оператором позиционного $ проецирования.

Тогда вам будет рекомендовано "не использовать вложенные массивы" и вместо этого выбрать вариант "более плоские" структуры данных, поскольку эти ответы уже подробно обсуждаются.

...