如何在不使用展开的情况下匹配多个数组元素?

时间:2014-06-04 00:39:33

标签: mongodb aggregation-framework

我有一个包含多个数组的文档的集合。这些通常都非常大,但为了解释,您可以考虑以下两个文档:

{
    "obj1": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "c" },
        { "a": "a", "b": "b" }
    ],
    "obj2": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "c" }
    ]
},
{
    "obj1": [
        { "a": "c", "b": "b" }
    ],
    "obj2": [
        { "a": "c", "b": "c" }
    ]
}

我们的想法是将数组中的匹配元素放到查询中。在多个数组中需要多个匹配,因此这不在投影和positional $运算符的范围内。期望的结果如下:

{
    "obj1": [
        { "a": "a", "b": "b" },
        { "a": "a", "b": "b" }
    ],
    "obj2": [
        { "a": "a", "b": "b" },
    ]
},

传统的方法是这样的:

db.objects.aggregate([
    { "$match": {
        "obj1": {
            "$elemMatch": { "a": "a", "b": "b" }
        },
        "obj2.b": "b"
    }},
    { "$unwind": "$obj1" },
    { "$match": {
        "obj1.a": "a",
        "obj1.b": "b"
    }},
    { "$unwind": "$obj2" },
    { "$match": { "obj2.b": "b" }},
    { "$group": {
        "_id": "$_id",
        "obj1": { "$addToSet": "$obj1" },
        "obj2": { "$addToSet": "$obj2" }
    }}
])

但是在这两个数组中使用$unwind会导致整个集合使用大量内存并减慢速度。 $addToSet也可能存在问题,并且为每个数组拆分$group阶段会使事情变得更慢。

所以我正在寻找一个不那么密集但过程相同的过程。

1 个答案:

答案 0 :(得分:3)

由于MongoDB 3.0我们有$filter运算符,这使得这非常简单:

db.objects.aggregate([
    { "$match": {
        "obj1": {
            "$elemMatch": { "a": "a", "b": "b" }
        },
        "obj2.b": "b"
    }},
    { "$project": {
      "obj1": {
        "$filter": {
          "input": "$obj1",
          "as": "el",
          "cond": {
            "$and": [
              { "$eq": [ "$$el.a", "a" ] },
              { "$eq": [ "$$el.b", "b" ] }
            ]
          }
        }
      },
      "obj2": {
        "$filter": {
          "input": "$obj2",
          "as": "el",
          "cond": { "$eq": [ "$$el.b", "b" ] }
        }
      }
    }}
])

MongoDB 2.6引入了$map运算符,该运算符可以在不需要$unwind的情况下对阵列进行操作。结合已添加到聚合框架的其他逻辑运算符和其他集合运算符,可以解决此问题和其他问题。

db.objects.aggregate([
    { "$match": {
        "obj1": {
            "$elemMatch": { "a": "a", "b": "b" }
        },
        "obj2.b": "b"
    }},
    { "$project": {
        "obj1": {
            "$setDifference": [
                { "$map": {
                    "input": "$obj1",
                    "as": "el",
                    "in": {
                         "$cond": [
                           { "$and": [
                                { "$eq": [ "$$el.a", "a" ] },
                                { "$eq": [ "$$el.b", "b" ] }
                            ]},
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        },
        "obj2": {
            "$setDifference": [
                { "$map": {
                    "input": "$obj2",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.b", "b" ] },
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

它的核心在$map运算符中,它通过允许处理所有数组元素而像内化$unwind一样工作,但也允许操作在同一语句中对这些数组元素进行操作。通常情况下,这将在几个管道阶段完成,但我们可以在一个$project$group$redact阶段进行处理。

在这种情况下,内部处理使用$cond运算符,该运算符结合逻辑条件,以便为truefalse返回不同的结果。在这里,我们使用$eq运算符来测试当前元素中包含的字段的值,其方式与使用单独的$match管道阶段的方式非常相似。 $and条件是另一个逻辑运算符,它用于组合元素上多个条件的结果,这与$elemMatch运算符在$match管道阶段中工作的方式非常相似。 / p>

最后,由于我们的$cond运算符用于返回当前元素的值,或false如果条件不是true,我们需要“过滤”任何{{1}来自数组的值产生了$map操作。这是$setDifference运算符用于比较两个输入数组并返回差异的位置。因此,当与仅包含false元素的数组进行比较时,结果将是从$map返回的元素,而$cond元素不会来自$unwind当条件不满足时。

结果只过滤数组中的匹配元素,而不必为$match$group和{{3}}分别运行单独的管道阶段。