【问题标题】:Conditionally Project Matching Array Item有条件地投影匹配的数组项
【发布时间】:2018-11-13 13:41:00
【问题描述】:

我有一个名为 questions 的集合,其中包含如下文档:

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

我想编写一个查询,如果特定的language_id 不存在,那么默认情况下应该返回带有1 的language_id

到目前为止,我已经尝试了两个查询:

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 都存在于数组中,则此查询返回两个元素。

【问题讨论】:

    标签: arrays mongodb mongodb-query aggregation-framework


    【解决方案1】:

    有“几种”方法:

    理想情况下,您拥有来自 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
              ]
            }
          }
        }
      }}
    ])
    

    $condif 条件的其他替代方法是使用$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 条件最多匹配“默认”情况,但无论如何您仍然需要“过滤掉”只有“首选”匹配。

    【讨论】:

    • 第一个查询将返回错误的结果,因为$indexOfArray 返回 -1,$arrayElemAt 根据文档给出数组中的最后一个元素。
    • @RahulSharma 啊!当然你的权利$ifNull 可能不是最好的事情。
    • 我想编写一个回退到使用第三个查询正确实现的language_id 1 的查询。我只是指出,如果 $indexOfArray 返回 -1,$arrayElemAt 将不会返回 null。无论如何感谢您对第三个查询的帮助。
    • @RahulSharma 我还是修改了第一个。 $ifNull 不起作用,因为没有返回 null,因此 $cond 在所有情况下都有效。
    猜你喜欢
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 2016-03-26
    • 2016-11-02
    • 1970-01-01
    • 2017-02-17
    • 2020-11-21
    相关资源
    最近更新 更多