Условно соответствующий элемент массива проекта - PullRequest
0 голосов
/ 04 июня 2018

У меня есть коллекция с именем questions с такими документами:

{
    "formats": [
        {
            "language_id": 1,
            "text": "question text1"
        },
        {
            "language_id": 2,
            "text": "question text 2"
        }
    ],
    "qid": "HQSRFA3T"
}

Я хочу написать запрос так, чтобы, если конкретный language_id отсутствует, то language_id с 1 долженбудет возвращен по умолчанию.

Пока я пробовал два запроса:

db.questions.aggregate([
  { 
    $match: {
      'qid': 'HQSRFA3T'
    }
  },
  {
    $project: {
      formats: {
        $ifNull: [
          { $filter: { input: '$formats', as: 'format', cond: {$eq: ['$$format.language_id', 3]}} },
          { $filter: { input: '$formats', as: 'format', cond: {$eq: ['$$format.language_id', 1]}} }
        ]
      },
      _id: 0
    }
  }
])

Этот запрос приводит к чему-то вроде этого:

{ "formats" : [ ] }

Тогда есть еще один запрос, который выглядит примерно так:

db.questions.aggregate([ { $match: {'qid': 'HQSRFA3T'}}, { $project: {
  formats: {
    $filter: {
      input: '$formats',
      as: 'format',
      cond: {
        $or: [
          { $eq: ['$$format.language_id', 1] },
          { $eq: ['$$format.language_id', 3] }
        ]
      }
    }
  },
  _id: 0
}}])

Этот запрос возвращает два элемента, если в массиве присутствуют оба language_id.

1 Ответ

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

Есть несколько способов:

В идеале у вас есть $indexOfArray из MongoDB 3.4, тогда вы можете использовать это в сочетании с $in:

db.questions.aggregate([
  { "$match": { "qid": "HQSRFA3T" } },
  { "$project": {
    "formats": {
      "$cond": {
        "if": { "$in": [ 3, "$formats.language_id"] },
        "then": { 
          "$arrayElemAt": [
            "$formats",
            { "$indexOfArray": [ "$formats.language_id", 3 ] }
          ]
        },
        "else": {
          "$arrayElemAt": [
            "$formats",
            { "$indexOfArray": [ "$formats.language_id", 1 ] }
          ]
        }
      }
    }}
  }
])

И если все, что вам действительно нужно, это соответствие "text", то небольшое изменение:

db.questions.aggregate([
  { "$match": { "qid": "HQSRFA3T" } },
  { "$project": {
    "text": {
      "$cond": {
        "if": { "$in": [ 3, "$formats.language_id"] },
        "then": { 
          "$arrayElemAt": [
            "$formats.text",
            { "$indexOfArray": [ "$formats.language_id", 3 ] }
          ]
        },
        "else": {
          "$arrayElemAt": [
            "$formats.text",
            { "$indexOfArray": [ "$formats.language_id", 1 ] }
          ]
        }
      }
    }}
  }
])

Это работает, потому что если $indexOfArrayвозвращает -1, указывая «не найдено», тогда $cond будет соответственно ветвиться:

В качестве альтернативы используйте $filter с $size:

db.questions.aggregate([
  { "$match": { "qid": "HQSRFA3T" } },
  { "$project": {
    "formats": {
      "$cond": {
        "if": { "$gt": [
          { "$size": { 
            "$filter": { 
              "input": "$formats",
               "cond": { "$eq": [ "$$this.language_id", 3 ] }
            }
          }},
          0
        ]},
        "then": {
          "$filter": {
            "input": "$formats",
            "cond": { "$eq": [ "$$this.language_id", 3 ] }
          }
        },
        "else": {
          "$filter": {
            "input": "$formats",
            "cond": { "$eq": [ "$$this.language_id", 1 ] }
          }
        }
      }
    }
  }}
])

Вы даже можете изменить в последней форме с помощью $arrayElemAt, чтобы просто вернуть «единственный» соответствующий элемент массива в позиции 0, если вы хотя быиметь MongoDB 3.2.

db.questions.aggregate([
  { "$match": { "qid": "HQSRFA3T" } },
  { "$project": {
    "formats": {
      "$cond": {
        "if": { "$gt": [
          { "$size": { 
            "$filter": { 
              "input": "$formats",
               "cond": { "$eq": [ "$$this.language_id", 3 ] }
            }
          }},
          0
        ]},
        "then": {
          "$arrayElemAt": [
            { "$filter": {
              "input": "$formats",
              "cond": { "$eq": [ "$$this.language_id", 3 ] }
            }},
            0
          ]
        },
        "else": {
          "$arrayElemAt": [
            { "$filter": {
              "input": "$formats",
              "cond": { "$eq": [ "$$this.language_id", 1 ] }
            }},
            0
          ]
        }
      }
    }
  }}
])

Другие альтернативы $cond для условия if используют $in, чтобы сопоставить сравнение наэлементы массива:

"if": { "$in": [ 3, "$formats.language_id" ] }

Но поскольку для этого требуется MongoDB 3.4, вы также можете использовать вместо этого оператор $indexOfArray.

В этом очень мало смыслапытатьсяВы можете «принудительно» сделать несколько совпадений в $filter, а затем в конечном итоге попытаться сбросить один из них, но вы «можете» сделать это с помощью $let:

db.questions.aggregate([
  { "$match": { "qid": "HQSRFA3T" } },
  { "$project": {
    "formats": {
      "$let": {
        "vars": {
          "formats": {
            "$filter": {
              "input": "$formats",
              "cond": {
                "$or": [
                  { "$eq": [ "$$this.language_id", 1 ] },
                  { "$eq": [ "$$this.language_id", 3 ] }
                ]
              }
            }
          }
        },
        "in": {
           "$cond": {
             "if": {
               "$gt": [
                 { "$size": {
                   "$filter": {
                     "input": "$$formats",
                     "cond": { "$eq": [ "$$this.language_id", 3 ] }
                   }
                 }},
                 0
               ]
             },
             "then": {
               "$filter": {
                 "input": "$$formats",
                 "cond": { "$eq": [ "$$this.language_id", 3 ] }
               }
             },
             "else": {
               "$filter": {
                 "input": "$$formats",
                 "cond": { "$eq": [ "$$this.language_id", 1 ] }
               }
             }
           }
        }
      }
    }
  }}
])

Так что это есть, но это просто дополнительная работа с небольшим усилением, поскольку в лучшем случае условие $or соответствует случаю «по умолчанию», и вам все еще необходимо «отфильтровать» только «предпочтительный»"в любом случае совпадать.

...