有条件地投影匹配数组项

Rah*_*rma 4 arrays mongodb mongodb-query aggregation-framework

我有一个questions用这样的文档命名的集合:

{
    "formats": [
        {
            "language_id": 1,
            "text": "question text1"
        },
        {
            "language_id": 2,
            "text": "question text 2"
        }
    ],
    "qid": "HQSRFA3T"
}
Run Code Online (Sandbox Code Playgroud)

我想写一个查询,如果特定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
    }
  }
])
Run Code Online (Sandbox Code Playgroud)

这个查询的结果是这样的:

{ "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
}}])
Run Code Online (Sandbox Code Playgroud)

如果两个元素language_id都存在于数组中,则此查询将返回两个元素。

Nei*_*unn 5

有“几种”方式:

理想情况下,您$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 ] }
          ]
        }
      }
    }}
  }
])
Run Code Online (Sandbox Code Playgroud)

如果你真正想要的是匹配"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 ] }
          ]
        }
      }
    }}
  }
])
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为如果$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 ] }
          }
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

如果您至少拥有 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
          ]
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

在其它替代$condif条件使用$in,以匹配的数组元素的比较:

"if": { "$in": [ 3, "$formats.language_id" ] }
Run Code Online (Sandbox Code Playgroud)

但由于这需要 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 ] }
               }
             }
           }
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

所以它就在那里,但它只是额外的工作,收益很小,因为$or条件充其量与“默认”情况匹配,并且无论如何您仍然需要“过滤掉”仅“首选”匹配。