Mongo 在二维数组的单个字段上设置交集

Mongo set intersection on single field which is a 2d array

我在 mongo 中有以下结构的文件。我想在 parent 中获取 children 的所有常见日期。

{"parent": 1, "child": "a", "date": "2016-02-01"},
{"parent": 1, "child": "a", "date": "2016-02-02"},
{"parent": 1, "child": "a", "date": "2016-02-03"},
{"parent": 1, "child": "b", "date": "2016-02-01"},
{"parent": 1, "child": "b", "date": "2016-02-03"},
{"parent": 2, "child": "a", "date": "2016-02-02"},
{"parent": 2, "child": "a", "date": "2016-02-03"},
{"parent": 2, "child": "b", "date": "2016-02-01"},
{"parent": 2, "child": "b", "date": "2016-02-02"}

为此,我使用聚合框架和以下管道来获取一组日期数组。

{
    $group: {
        _id: {
            parent: "$parent",
            child: "$child"
        },
        dates: {
            $push: "$date"
        }
    }
}, {
    $group: {
        _id: "$_id.parent",
        dates: {
            $push: "$dates"
        }
    }
}

输出结果为:

[ 
    {
        "_id" : 1,
        "dates" : [ 
            [ 
                "2016-02-01", 
                "2016-02-03"
            ], 
            [ 
                "2016-02-01", 
                "2016-02-02", 
                "2016-02-03"
            ]
        ]
    }, 
    {
        "_id" : 2,
        "dates" : [ 
            [ 
                "2016-02-01", 
                "2016-02-02"
            ], 
            [ 
                "2016-02-02", 
                "2016-02-03"
            ]
        ]
    }
]

我现在想要获取每个文档的二维数组中的所有公共日期,并且我尝试添加 $projection 阶段和 $setIntersection。但据我了解,$setIntersection 需要一组定义明确的字段或数组 - 使用 $setIntersection: "$dates" 无法按预期工作。

感谢任何帮助!

附加信息:child 类型的数量是可变的

预期输出:

[ 
    {
        "_id" : 1.0000000000000000,
        "dates" : [
            "2016-02-01", 
            "2016-02-03"
        ]
    }, 
    {
        "_id" : 2.0000000000000000,
        "dates" : [ 
            "2016-02-02"
        ]
    }
]

那么在 MongoDB 3.2 中你可以使用 $arrayElemAt to get each element of the two dimensions and feed that to $setIntersection:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "parent": "$parent",
            "child": "$child"
        },
        "dates": { "$push": "$date" }
    }}, 
    { "$group": {
        "_id": "$_id.parent",
        "dates": { "$push": "$dates" }
    }},
    { "$project": {
        "dates": {
            "$setIntersection": [
                { "$arrayElemAt": [ "$dates", 0 ] },
                { "$arrayElemAt": [ "$dates", 1 ] }
            ]
        }
    }}
])

但实际上,您正在查看的特定问题可以通过更简单的方式解决。基本上您需要做的就是计算 parent 每个日期出现的 child 次。超过一个表示有两个或更多 children 共享日期:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "parent": "$parent",
            "date": "$date"
        },
        "count": { "$sum": 1 }
    }},
    { "$match": { "count": { "$gt": 1 } } },
    { "$group": {
        "_id": "$_id.parent",
        "dates": { "$push": "$_id.date" }
    }}
])    

因此没有理由比较数组,因为简单的分组元素计数就可以告诉您 "set intersection" 是什么。

同样的原则适用于引入聚合框架的每个 MongoDB 版本。

两者都给你相同的结果:

{ "_id" : 1, "dates" : [ "2016-02-03", "2016-02-01" ] }
{ "_id" : 2, "dates" : [ "2016-02-02" ] }

还注意到 "sets" 不被认为是有序的,$group 发出的键的顺序也不被认为是有序的。

这里可以做的是统计一个parent的child人的个数,统计一个parent的每个child的日期数。

然后获取日期数等于 parent 的 children 数的所有日期,这将给出预期的输出。

这是我尝试过的方法,但可能会有更好的解决方案。

db.coll.aggregate([
    {
        $group: {
            _id: {
                parent: "$parent",
                child: "$child"
            },
            dates: {
                $push: "$date"
            }
        }
    },
    {
        $group: {
            _id: "$_id.parent",
            total_children: {$sum : 1},
            dates: {
                $push: "$dates"
            }
        }
    },
    {
        $unwind : "$dates"
    },
    {
        $unwind : "$dates"
    },
    {
        $group : {
            _id : {
                parent : "$_id",
                dates : "$dates"
            },
            total_children : {$first : "$total_children"},
            total_dates : {$sum : 1}
        }
    },
    {
        $project : {
            _id : 1,
            tempEq : {$eq : ["$total_children", "$total_dates"]}
        }
    },
    {
        $match : {'tempEq' : true}
    },
    {
        $group : {
            _id : '$_id.parent',
            dates : {$addToSet : "$_id.dates"}
        }
    }
])

这给出了以下输出:

{ "_id" : 1, "dates" : [ "2016-02-01", "2016-02-03" ] }
{ "_id" : 2, "dates" : [ "2016-02-02" ] }

希望对您有所帮助。