MongoDB的$ setUnion不适用于$ push生成的数组

时间:2018-08-27 07:08:09

标签: mongodb

我正在尝试对某些集合(MongoDB 3.4)执行复杂的聚合查询。在这种聚合中,我需要消除重复项,为此,我希望使用$setUnion运算符。

请考虑以下内容:

const a = ObjectId();
const b = ObjectId();
const c = ObjectId();
const d = ObjectId();

db.getCollection('test').insertMany([
{
    arr: [a, b]
},
{
    arr: [a, b, c]
},
{
    arr: [d]
},
{
    arr: [a, d]
}
])

现在我执行此聚合:

db.getCollection('test').aggregate([
{
    $group: {
        _id: null,
        dups: {$push: '$arr'}
    }
},
{
    $project: {
        _id: 0,
        noDupsDoesNotWork: {
            $setUnion: '$dups'
        }
}
}
])

并收到:

{
    "noDupsDoesNotWork" : [ 
        [ 
            ObjectId("5b839b38f6db291c78201c0d"), 
            ObjectId("5b839b38f6db291c78201c0e")
        ], 
        [ 
            ObjectId("5b839b38f6db291c78201c0d"), 
            ObjectId("5b839b38f6db291c78201c0e"), 
            ObjectId("5b839b38f6db291c78201c0f")
        ], 
        [ 
            ObjectId("5b839b38f6db291c78201c0d"), 
            ObjectId("5b839b38f6db291c78201c10")
        ], 
        [ 
            ObjectId("5b839b38f6db291c78201c10")
        ]
    ]
}

这是我不了解的,因为据说$setUnion可用于数组数组。在我的示例中,我期望它对4个数组执行集合并集。 但是,如果执行此聚合查询:

db.getCollection('test').aggregate([
{
    $group: {
        _id: null,
        dups: {$push: '$arr'}
    }
},
{
    $project: {
        _id: 0,
        flattened: {
            $reduce: {
                input: '$dups',
                initialValue: [],
                in: {$concatArrays: ['$$value', '$$this']}
            }
        }
    }
},
{
    $project: {
        noDups: {$setUnion: '$flattened'}
    }
}
])

然后我会得到预期的结果:

{
    "noDups" : [ 
        ObjectId("5b839b38f6db291c78201c0d"), 
        ObjectId("5b839b38f6db291c78201c0e"), 
        ObjectId("5b839b38f6db291c78201c0f"), 
        ObjectId("5b839b38f6db291c78201c10")
    ]
}

但是这次$setUnion适用于单个对象数组,而不是数组数组。

我更加困惑,因为如果我明确传递四个子数组,我将获得预期的结果:

db.getCollection('test').aggregate([
{
    $group: {
        _id: null,
        dups: {$push: '$arr'}
    }
},
{
    $project: {
        _id: 0,
        explicit: {
            $setUnion: [
                {$arrayElemAt: ['$dups', 0]},
                {$arrayElemAt: ['$dups', 1]},
                {$arrayElemAt: ['$dups', 2]},
                {$arrayElemAt: ['$dups', 3]},
            ]
        }
    }
}
])

我在这里想念什么?

为清楚起见,这是所需的输出(上面已详细说明,但再次添加了):

{
    "noDups" : [ 
        ObjectId("5b839b38f6db291c78201c0d"), 
        ObjectId("5b839b38f6db291c78201c0e"), 
        ObjectId("5b839b38f6db291c78201c0f"), 
        ObjectId("5b839b38f6db291c78201c10")
    ]
}

1 个答案:

答案 0 :(得分:0)

为什么会这样? 好吧,不管你信不信,这实际上是 Mongo 的一个“特性”。 您可以跟踪一个打开的 feature change request。但让我们了解一下why

<块引用>

有一类表达式可以接受多个参数,表示为数组。 (在代码中,这些继承自 ExpressionNary。)对于任何这样的 n-ary 表达式,我将称之为 $nary,语法 {$nary: "$myArg"} 只是 {$nary: ["$myArg "]}。这两个表达的意思相同。

所以这一类独特的表达式包括许多运算符,其中之一是 $setUnion。 Mongo 的表达式语言只是将这些运算符的任何 '$arg' 解析为 ['$arg']。 这就是为什么在您使用 $dups 表达式时会发生“意外”行为,而在您明确写入相同值时不会发生的原因。

对于没有阅读您的完整问题的人,我将再次重复这个(最好的)词,您想在使用 $setUnion 展平数组后使用 $reduce 像这样:

{
    $addFields: {
        flat: {
            $setUnion: {
                $reduce: {
                    input: '$myArray',
                    initialValue: [],
                    in: {$concatArrays: ['$$this', '$$value']}
                }
            }
        }
    }
}