【问题标题】:How to filter array in subdocument with MongoDB [duplicate]如何使用MongoDB过滤子文档中的数组[重复]
【发布时间】:2013-02-13 13:14:21
【问题描述】:

我在子文档中有这样的数组

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 1
        },
        {
            "a" : 2
        },
        {
            "a" : 3
        },
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

我可以为 > 3 过滤子文档

下面是我的预期结果

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

我尝试使用$elemMatch,但返回数组中的第一个匹配元素

我的查询:

db.test.find( { _id" : ObjectId("512e28984815cbfcb21646a7") }, { 
    list: { 
        $elemMatch: 
            { a: { $gt:3 } 
            } 
    } 
} )

结果返回数组中的一个元素

{ "_id" : ObjectId("512e28984815cbfcb21646a7"), "list" : [ { "a" : 4 } ] }

我尝试将聚合与 $match 一起使用,但不起作用

db.test.aggregate({$match:{_id:ObjectId("512e28984815cbfcb21646a7"), 'list.a':{$gte:5}  }})

返回数组中的所有元素

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 1
        },
        {
            "a" : 2
        },
        {
            "a" : 3
        },
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

我可以过滤数组中的元素以获得预期结果吗?

【问题讨论】:

    标签: mongodb filter mongodb-query aggregation-framework


    【解决方案1】:

    使用aggregate 是正确的方法,但是您需要在应用$match 之前将$unwind list 数组,以便您可以过滤单个元素,然后使用$group 将其重新组合在一起:

    db.test.aggregate([
        { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
        { $unwind: '$list'},
        { $match: {'list.a': {$gt: 3}}},
        { $group: {_id: '$_id', list: {$push: '$list.a'}}}
    ])
    

    输出:

    {
      "result": [
        {
          "_id": ObjectId("512e28984815cbfcb21646a7"),
          "list": [
            4,
            5
          ]
        }
      ],
      "ok": 1
    }
    

    MongoDB 3.2 更新

    从 3.2 版本开始,您可以使用新的 $filter 聚合运算符更有效地执行此操作,只需在 $project 中包含您想要的 list 元素:

    db.test.aggregate([
        { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
        { $project: {
            list: {$filter: {
                input: '$list',
                as: 'item',
                cond: {$gt: ['$$item.a', 3]}
            }}
        }}
    ])
    

    【讨论】:

    • 聚合操作是修改文档还是只进行选择?
    • @Cherif aggregate 不会影响文档本身。
    • 如果您想将该查询的结果发布给客户端,您会怎么做?
    • 此解决方案是否利用数组元素内部的索引(如果数组元素本身就是一个文档)?假设在数组元素的某个路径上有一个索引。
    • 使用cond: { $eq: ['$$item.a', <value> ] } 根据精确的等效值过滤元素。
    【解决方案2】:

    使用$filter aggregation

    根据指定选择要返回的数组子集 健康)状况。返回一个仅包含与 健康)状况。返回的元素按照原来的顺序排列。

    db.test.aggregate([
        {$match: {"list.a": {$gt:3}}}, // <-- match only the document which have a matching element
        {$project: {
            list: {$filter: {
                input: "$list",
                as: "list",
                cond: {$gt: ["$$list.a", 3]} //<-- filter sub-array based on condition
            }}
        }}
    ]);
    

    【讨论】:

    • 请注意,如果您使用的是Atlas,则$filter 不能用于免费层。
    【解决方案3】:

    如果需要多个匹配的子文档,上述解决方案效果最佳。 如果需要单个匹配的子文档作为输出,$elemMatch 也非常有用

    db.test.find({list: {$elemMatch: {a: 1}}}, {'list.$': 1})
    

    结果:

    {
      "_id": ObjectId("..."),
      "list": [{a: 1}]
    }
    

    【讨论】:

    • 这对我来说是最好的解决方案。但是第二个参数仅适用于原始类型。由于我在数组中有一个对象,我的解决方案是:ShoppingCartModel.findOne({"_id": ctx.params.idCart, "active" : true, products: {$elemMatch : {"products.active" : true}}})(我只过滤活动:真正的购物车和产品)
    猜你喜欢
    • 2016-09-01
    • 2018-01-26
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 2021-09-29
    相关资源
    最近更新 更多