在不使用展开的情况下匹配数组中的两个相等字段

时间:2014-06-04 03:17:32

标签: mongodb aggregation-framework

问题是给定的文档有两个数组,每个数组都包含文档作为其元素,我想找到基本上具有的文档:

"obj1.a" === "obj2.b"

所以给定样本文档,但实际上期望更大的数组,那么如何做到这一点?:

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

一种方法可能是将这些与JavaScript和$where运算符进行比较,但是从JavaScript中循环大型数组听起来并不是很有利。

另一种方法是使用聚合框架进行比较,但这涉及将两个数组相互展开,这可以创建大量要在管道中处理的文档:

db.objects.aggregate([
    { "$unwind": "$obj1" },
    { "$unwind": "$obj2" },
    { "$project": {
        "match": { "$eq": [ "$obj1.a", "$obj2.b" ] }
    }},
    { "$group": {
        "_id": "$_id",
        "match": { "$max": "$match" }
    }},
    { "$match": { "match": true } }
])

如果需要考虑性能,很容易看出通过$project$group实际处理的文档数量最终会比集合中的原始文档大很多倍。

因此,为了做到这一点,必须有一些比较数组元素的方法,而不需要在这些数组上执行$unwind,最终将文档重新组合在一起。怎么可以这样做?

1 个答案:

答案 0 :(得分:3)

您可以使用MongoDB 2.6中引入的$map运算符获得此类结果。这通过获取输入数组并允许在每个元素上计算表达式来生成新数组作为结果:

db.objects.aggregate([
    { "$project": {
        "match": {
            "$size": {
                "$setIntersection": [
                    { "$map": {
                        "input": "$obj1",
                        "as": "el",
                        "in": { "$concat": ["$$el.a",""] }
                    }},
                    { "$map": {
                        "input": "$obj2",
                        "as": "el",
                        "in": { "$concat": ["$$el.b",""] }
                    }}
                ]
            }
        }
    }},
    { "$match": { "match": { "$gte": 1 } } }
])

此处与$setIntersection$size运算符一起使用。由于$map仅返回您要比较的元素的属性值,因此最终会得到两个仅包含这些值的数组。

唯一的一点就是 $map 的“in”选项当前要求运算符出现在其参数的Object {}符号中。你现在不能说:

"in": "$$el.a"

为了解决这个问题,我们使用 $concat 将字符串值与空字符串连接起来。其他运算符可以用于不同类型的偶数$ifNull,这些类型相当通用并且可以解决“类型”问题

"in": { "$ifNull": [ "$$el.a", false ] }

包装它们的$setIntersection用于确定那些“集合”的哪些值相同,并将其结果作为另一个仅包含匹配值的数组返回。

最后,这里的$size运算符是一个聚合方法,它将数组的实际“大小”作为整数返回。因此,可以在以下$match中使用此选项,然后过滤掉未返回“{1}}或更大”{size}值的任何结果。

基本上,这将完成在四个单独阶段中完成的所有工作,其中前两个阶段在两个简单的过程中指数增长要处理的文档数量,所有这些都不会增加作为输入接收的文档数量。