有条件的项目匹配数组项目

时间:2018-06-04 09:00:29

标签: arrays mongodb mongodb-query aggregation-framework

我有一个名为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 :(得分:2)

有几种方式:

理想情况下,您拥有MongoDB 3.4中的$indexOfArray,然后您可以将其与$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 ] }
          }
        }
      }
    }
  }}
])

如果您至少拥有MongoDB 3.2,您甚至可以使用$arrayElemAt改变最后一个表单,只返回位置0的“单个”匹配数组元素。

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
          ]
        }
      }
    }
  }}
])

if条件的$cond上的其他替代方法正在使用$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条件匹配“默认”情况,你仍然需要“过滤掉”只为“首选”匹配。< / p>