有条件的项目匹配数组项

5

我有一个名为 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个回答

5

有几种方法:

理想情况下,您可以使用 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 ] }
          }
        }
      }
    }
  }}
])

你甚至可以使用 $arrayElemAt 在最后一个表单上进行变化,如果你至少拥有 MongoDB 3.2,它只会返回位置为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条件匹配“默认”情况,而你仍然需要“过滤掉”仅针对“首选”匹配的情况。

第一个查询将返回错误的结果,因为 $indexOfArray 返回 -1,而根据文档 $arrayElemAt 给出数组中的最后一个元素。 - Rahul Sharma
@RahulSharma 哎呀!你当然是对的,$ifNull 可能不是最好的选择。 - Neil Lunn
我想编写一个查询,它会回退到选择 language_id 1,这可以通过第三个查询正确实现。我只是想指出,如果 $indexOfArray 返回 -1,则 $arrayElemAt 不会返回 null。无论如何,感谢您对第三个查询的帮助。 - Rahul Sharma
@RahulSharma 我已经修改了第一个。$ifNull 不起作用,因为没有返回 null 的东西,因此 $cond 在所有情况下都有效。 - Neil Lunn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接