【问题标题】:Select sub-documents where field's value is in an some array选择字段值在某个数组中的子文档
【发布时间】:2017-08-04 07:25:27
【问题描述】:

我想根据子文档进行过滤,但实际上我正在为每个子文档重复文档。如果是这样的话,我想要一份文件和一份子文件清单。

我的数据如下:

{
    "_id" : ObjectId("582eeb5f75f58055246bd22d"),
    "filename" : "file1",
    "cod" : NumberLong(90),
    "subdocs" : [
        {
            "length" : NumberLong(10),
            "desc" : "000"
        },
        {
            "length" : NumberLong(15),
            "desc" : "011"
        },
        {
            "length" : NumberLong(30),
            "desc" : "038"
        }
    ]
}
{
    "_id" : ObjectId("582eeb5f75f58055246bd22e"),
    "filename" : "file2",
    "cod" : NumberLong(95),
    "subdocs" : [
        {
            "length" : NumberLong(11),
            "desc" : "000"
        },
        {
            "length" : NumberLong(21),
            "desc" : "018"
        },
        {
            "length" : NumberLong(41),
            "desc" : "008"
        }
    ]
}

我正在使用此查询过滤 subdocs

上的 desc (000, 011)
db.ftmp.aggregate( 
    { $match: 
        { "subdocs.desc": 
            { $in: ["000", "011"] } 
        }
    }, 
    { $unwind : "$subdocs" }, 
    { $match : 
        { "subdocs.desc" : 
            { $in:["000", "011"] } 
        }
    }
)

但结果显示 3 个文档,每个与该查询匹配的子文档对应 1 个文档。

{
    "_id" : ObjectId("582eeb5f75f58055246bd22d"),
    "filename" : "file1",
    "cod" : NumberLong(90),
    "subdocs" : {
        "length" : NumberLong(10),
        "desc" : "000"
    }
}
{
    "_id" : ObjectId("582eeb5f75f58055246bd22d"),
    "filename" : "file1",
    "cod" : NumberLong(90),
    "subdocs" : {
        "length" : NumberLong(15),
        "desc" : "011"
    }
}
{
    "_id" : ObjectId("582eeb5f75f58055246bd22e"),
    "filename" : "file2",
    "cod" : NumberLong(95),
    "subdocs" : {
        "length" : NumberLong(11),
        "desc" : "000"
    }
}

但我想得到:file1 的子文档为 desc 000 和 011,file2 的子文档为 000

{
    "_id" : ObjectId("582eeb5f75f58055246bd22d"),
    "filename" : "file1",
    "cod" : NumberLong(90),
    "subdocs" : [
        {
            "length" : NumberLong(10),
            "desc" : "000"
        },
        {
            "length" : NumberLong(15),
            "desc" : "011"
        }
    ]
}
{
    "_id" : ObjectId("582eeb5f75f58055246bd22e"),
    "filename" : "file2",
    "cod" : NumberLong(95),
    "subdocs" : {
        "length" : NumberLong(11),
        "desc" : "000"
    }
}

这样做的正确方法是什么?有什么想法吗?

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    首先使用$unwind 运算符,如answer 中所述,将导致应用程序性能下降,因为展开数组会导致更多文档在管道中处理。从 MongoDB 2.6 开始有更好的方法来实现这一点。

    话虽如此,这对于 MongoDB 3.2 中的 $filter 运算符 new 来说是一项完美的工作。

    最有效的方法是在 MongoDB 3.4 中。 MongoDB 3.4 为聚合框架引入了 $in 数组运算符,可在 $filter conditional 表达式中使用,当计算结果为 true 时,将子文档包含在结果数组中。

    let values = [ '000', '011' ];
    
    db.collection.aggregate([ 
        { "$project": { 
            "filename": 1, 
            "cod": 1, 
            "subdocs": { 
                "$filter": { 
                    "input": "$subdocs", 
                    "as": "s", 
                    "cond": { "$in": [ "$$s.desc", values ] }
                } 
            } 
        }} 
    ])
    

    在 MongoDB 3.2 中,我们需要一种稍微不同的方法,因为我们可以在那里使用 $in 运算符。但幸运的是,我们有 $setIsSubset 运算符,您可能已经猜到了对两个数组执行集合操作,如果第一个数组是第二个数组的子集,则返回 true。因为$setIsSubset第一个表达式必须是一个数组,所以需要在我们的管道中将desc字段设为一个数组。为此,我们只需使用[] 括号创建array field which is new MongoDB 3.2

    db.collection.aggregate([ 
        { "$project": { 
            "filename": 1, 
            "cod": 1, 
            "subdocs": { 
                "$filter": { 
                    "input": "$subdocs", 
                    "as": "s", 
                    "cond": { "$setIsSubset": [ [ "$$s.desc" ], values ] }
                } 
            } 
        }} 
    ])
    

    MongoDB 3.0 对我来说已经死了,但如果由于某些原因您正在运行该版本,您可以使用 $literal 运算符返回集合操作所需的一个元素数组和 $setDifference 运算符。这留给读者作为练习。

    【讨论】:

      【解决方案2】:

      您只需要添加 $group 和 $push。首先,您 $unwind 子文档以应用 $match,然后在 id 上应用 $group,然后 $push 分组的子文档。

      db.ftmp.aggregate({
          $unwind: "$subdocs"
      }, {
          $match: {
              "subdocs.desc": {
                  $in: ["000", "011"]
              }
          }
      }, {
          $group: {
              _id: "$_id",
              subdocs: {
                  $push: "$subdocs"
              },
              filename: {
                  $first: "$filename"
              },
              cod: {
                  $first: "$cod"
              }
          }
      })
      

      【讨论】:

      • @ChristianSilva 这不是你应该怎么做的,你的 MongoDB 版本是什么?
      • 是的,查找查询也可以完成这项工作
      • db.collection.find({subdocs : {$elemMatch : {desc :{ $in: ["000", "011"] }. }}}) 我想这也会给你结果相同
      • @Styvane 我正在使用 3.2.10,我该怎么做?我不应该使用聚合吗?。
      • @Parshuram,该查询未过滤
      猜你喜欢
      • 1970-01-01
      • 2014-11-15
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多