Как вернуть содержимое вложенного индексированного поля с некоторыми полями верхнего уровня с помощью Mongodb? - PullRequest
1 голос
/ 26 мая 2020

Рассмотрим следующие документы:

{
  "chapterNumber": "1",
  "contents": [
    {
      "paragraphNumber": "1 ",
      "paragraphCleanText": "cleaned content 1",
      "contents": [
        "not clean content",
        {
          "p": null
        }
      ]
    },
    {
      "paragraphNumber": "1 ",
      "paragraphCleanText": "cleaned content 2",
      "contents": [
        "not clean content",
        {
          "p": null
        }
      ]
    },
  ]
}
{
  "chapterNumber": "2",
  "contents": [
    {
      "paragraphNumber": "1 ",
      "paragraphCleanText": "cleaned content 3",
      "contents": [
        "not clean content",
        {
          "p": null
        }
      ]
    },
    {
      "paragraphNumber": "1 ",
      "paragraphCleanText": "cleaned content 4",
      "contents": [
        "not clean content",
        {
          "p": null
        }
      ]
    },
  ]
}

Если я сделаю индекс в поле paragraphCleanText, а затем выдаю запрос для поиска этой строки cleaned content 3, есть ли способ вернуть следующую структуру из простого или оптимизированного запроса?

{
  "chapterNumber": "2",
  "paragraphNumber": "1 ",
  "paragraphCleanText": "cleaned content 3"
}

Ответы [ 2 ]

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

Нашел довольно простое решение для моего варианта использования:

  1. добавить индекс в поле paragraphCleanText, плюс chapterNumber и paragraphNumber в качестве дополнительных полей
  2. сделать следующий запрос db.yourCollection.find({"contents.paragraphCleanText" : { $regex: /^ some text that is present in the field .*/ } })._addSpecial( "$returnKey", true ) `
0 голосов
/ 26 мая 2020

Вам нужно использовать aggregation-pipeline для этого:

1. Если ваш "contents.paragraphCleanText" уникален:

Запрос:

db.collection.aggregate([
    /** Filter docs based on condition */
    { $match: { "contents.paragraphCleanText": "cleaned content 3" } },
    {
      $project: {
        _id: 0,
        chapterNumber: 1,
        /** contents will be an object, which is matched object, as `$filter` will return an array of matched objects, we're picking first object - assuming `contents.paragraphCleanText` is unique,
          *  Just in case if you've multiple objects that matches with given condition use `$reduce` instead of `$filter + $arrayElemAt` */
        contents: {
          $arrayElemAt: [
            { $filter: { input: "$contents", cond: { $eq: [ "$$this.paragraphCleanText", "cleaned content 3" ] } } },
            0
          ]
        }
      }
    },
    {
      $project: {
        chapterNumber: 1,
        paragraphNumber: "$contents.paragraphNumber",
        paragraphCleanText: "$contents.paragraphCleanText"
      }
    }
  ])

Тест: mongoplayground

Примечание: Если вы хотите, чтобы весь объект из массива contents соответствовал условию тогда вы можете просто использовать $ elemmatch-projection-operator или $ - positional-projection-operator , но поскольку вам не нужен весь объект и только несколько полей из сопоставленных объект, то вам нужно использовать параметр агрегации projection в .find() не может преобразовывать поля - он способен только включать или исключать поля из документа, поэтому для этого вы будете использовать этап агрегации $project.

Если ваш "contents.paragraphCleanText" может иметь дубликаты, т.е. внутри массива contents может быть несколько объектов с "contents.paragraphCleanText": "cleaned content 3":

Запрос:

  db.collection.aggregate([
    { $match: { "contents.paragraphCleanText": "cleaned content 3" } },
    {
      $project: {
        _id: 0,
        chapterNumber: 1,
        contents: {
          $reduce: {
            input: "$contents",
            initialValue: [], // create an empty array to start with 
            in: {
              $cond: [
                {
                  $eq: [ "$$this.paragraphCleanText", "cleaned content 3" ] // Condition to check
                },
                { // If an object has matched with condition concatinate array converted that object into holding array
                  $concatArrays: [ "$$value", [ { paragraphCleanText: "$$this.paragraphCleanText", paragraphNumber: "$$this.paragraphNumber" } ] ]
                },
                "$$value" // If current object is not matched return holding array as is 
              ]
            }
          }
        }
      }
    },
    {$unwind : '$contents'} // Purely optional stage - add this stage & test output to decide whether to include or not & add `$project` stage after this stage
  ])

Тест: игровая площадка

...