【问题标题】:Return first element if no match found in array如果在数组中找不到匹配项,则返回第一个元素
【发布时间】:2018-10-03 15:11:30
【问题描述】:

我有以下文件:

{
    _id: 123,
    state: "AZ",
    products: [
        {
            product_id: 1,
            desc: "P1"
        },
        {
            product_id: 2,
            desc: "P2"
        }   
    ]
}

我需要编写一个查询以从 products 数组中返回一个元素,其中 state 为“AZ”,product_id 为 2。如果找不到匹配的 product_id,则返回 products 数组中的第一个(或任何)元素.

例如:如果 product_id 为 2(找到匹配项),那么结果应该是:

products: [
    {
        product_id: 2,
        desc: "P2"
    }   
]

如果 product_id 为 3(未找到),那么结果应该是:

products: [
    {
        product_id: 1,
        desc: "P1"
    }   
]

找到匹配项时我能够满足一个条件,但不确定如何满足同一查询中的第二个条件:

db.getCollection('test').find({"state": "AZ"}, {_id: 0, state: 0, products: { "$elemMatch": {"product_id": "2"}}})

我也尝试使用聚合管道,但找不到可行的解决方案。

注意:这与以下问题不同,因为如果未找到匹配项,我需要返回默认元素: Retrieve only the queried element in an object array in MongoDB collection

【问题讨论】:

  • @AnthonyWinzlet,我需要返回一个默认元素以防找不到匹配项。在您提到的重复问题中没有提出这个问题。

标签: mongodb aggregation-framework


【解决方案1】:

你可以试试下面的聚合

基本上你需要$filterproducts数组并检查$condition,如果它不包含任何元素或等于[]那么你必须@987654323 @ 与 products 数组的第一个元素。

db.collection.aggregate([
  { "$addFields": {
    "products": {
      "$cond": [
        {
          "$eq": [
            { "$filter": {
              "input": "$products",
              "cond": { "$eq": ["$$this.product_id", 2] }
            }},
            []
          ]
        },
        { "$slice": ["$products", 1] },
        { "$filter": {
          "input": "$products",
          "cond": { "$eq": ["$$this.product_id", 2] }
        }}
      ]
    }
  }}
])

甚至使用$let聚合

db.collection.aggregate([
  { "$addFields": {
    "products": {
      "$let": {
        "vars": {
          "filt": {
            "$filter": {
              "input": "$products",
              "cond": { "$eq": ["$$this.product_id", 2] }
            }
          }
        },
        "in": {
          "$cond": [
            { "$eq": ["$$filt", []] },
            { "$slice": ["$products", 1] },
            "$$filt"
          ]
        }
      }
    }
  }}
])

【讨论】:

  • 谢谢安东尼。由于此解决方案仅适用于 MongoDB 3.4 及更高版本,我想知道是否只需将 $addFields 更改为 $project 以使其适用于 v3.2,因为我只能访问 3.2。使用 $project 不起作用。我也会在 3.4 和更高版本上对此进行测试,但想看看如何为旧版本编写此查询?
  • 如果你使用$project而不是$addFields,它会起作用,因为所有的运算符都是在3.2版中引入的。这是工作示例mongoplayground.net/p/BFD4CucD86t
  • 太好了,这行得通!感谢您提供工作示例。
【解决方案2】:

如果您不在乎返回哪个元素,那么这就是要走的路(如果没有匹配,您将获得数组中的最后一个元素,因为$indexOfArray 将返回 -1):

db.getCollection('test').aggregate([{
    $addFields: {
        "products": {
            $arrayElemAt: [ "$products", { $indexOfArray: [ "$products.product_id", 2 ] } ]
        },
    }
}])

如果您想要第一个,请改为执行此操作($max 将负责将 -1 转换为索引 0,即第一个元素):

db.getCollection('test').aggregate([{
    $addFields: {
        "products": {
            $arrayElemAt: [ "$products", { $max: [ 0, { $indexOfArray: [ "$products.product_id", 2 ] } ] } ]
        },
    }
}])

这是一个同样适用于 v3.2 的版本:

db.getCollection('test').aggregate([{
    "$project": {
        "products": {
            $slice: [{
                $concatArrays: [{
                    $filter: {
                        "input": "$products",
                        "cond": { "$eq": ["$$this.product_id", 2] }
                    }},
                    "$products" // simply append the "products" array
                   // alternatively, you could append only the first or a specific item like this [ { $arrayElemAt: [ "$products", 0 ] } ]
                ]
            },
            1 ] // take first element only
        }
    }
}])

【讨论】:

  • 感谢@dnickless,这仅适用于 MongoDB 3.4 及更高版本。我们能有一个同样适用于 3.2 的解决方案吗?我也会在 3.4+ 上测试它,但我现在需要在 3.2 上解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
相关资源
最近更新 更多