【问题标题】:How to use MongoDB aggregation for general purpose set operations (union, intersection, difference)如何将 MongoDB 聚合用于通用集合操作(​​联合、交集、差异)
【发布时间】:2014-04-27 23:27:02
【问题描述】:

我遇到了一些特殊目的的集合操作实现,但一般情况下没有。执行集合操作的一般情况是什么(特别是交集、并集、对称差)。在 $where 或 map reduce 中使用 javascript 更容易弄清楚,但我想知道如何在聚合中执行此操作以获得本机性能。

用一个例子来说明这个问题的更好方法。假设我有 2 个数组/集的记录:

db.colors.insert({
    _id: 1,
    left : ['red', 'green'],
    right : ['green', 'blue']
});

我想找到'left'和'right'数组的并集、交集和差异。更好的是,我想找到图片:

联合 --> ['red', 'green', 'blue']

交叉口 --> ['green']

对称差分 --> ['red', 'blue']

【问题讨论】:

  • 这个问题的灵感来自answer。我找到了几个具体案例,但不是我要寻找的一般案例。
  • @WiredPrairie,鉴于您链接到的那个是我的,我可以向您保证它们不是重复的问题。您链接到的是一个非常具体的案例,其中找到了一个子集。这是一个关于交叉、联合和差异的更广泛更一般的案例问题。
  • 我仍然把它作为一个结束,因为这比一个非常长的 Stack Overflow 问题和答案更适合作为 wiki 文章或博客文章,特别是因为真的没有一个好的答案已经提到过(这确实是 3 个问题)。
  • 在 StackOverflow 上投票是生活中可能永远无法完全理解的众多谜团之一。 :)

标签: mongodb set aggregation-framework set-intersection


【解决方案1】:

仅限 2.6+ 版:

从 MongoDB 2.6 版开始,这变得容易得多。您现在可以执行以下操作来解决此问题:

联合

db.colors.aggregate([
    {'$project': {  
                    union:{$setUnion:["$left","$right"]}
                 }
    }
]);

交叉口

db.colors.aggregate([
    {'$project': {  
                  int:{$setIntersection:["$left","$right"]}
                 }
    }
]);

相对补语

db.colors.aggregate([
    {'$project': {  
                    diff:{$setDifference:["$left","$right"]}
                 }
    }
]);

对称差分

db.colors.aggregate([
    {'$project': {  
                    diff:{$setUnion:[{$setDifference:["$left","$right"]}, {$setDifference:["$right","$left"]}]}
                 }
    }
]);

注意:有一个ticket 请求将对称差异添加为核心功能,而不必将两个集合差异合并。

【讨论】:

    【解决方案2】:

    这三个中最容易使用聚合的是交集**。一般情况下可以使用聚合来完成,如下所示:

    路口:

    db.colors.aggregate([
        {'$unwind' : "$left"},
        {'$unwind' : "$right"},
        {'$project': {  
                        value:"$left", 
                        same:{$cond:[{$eq:["$left","$right"]}, 1, 0]}
                     }
        },
        {'$group'  : { 
                        _id: {id:'$_id', val:'$value'}, 
                        doesMatch:{$max:"$same"}
                     }
        },
        {'$match'   :{doesMatch:1}},
    ]);
    

    另外两个变得有点棘手。据我所知,没有办法将同一文档中的两个单独字段组合在一起。在 $project 管道阶段有一个 $add、$combine 或 $addToSet 会很好,但是这不存在。因此,我们能做的最好的事情就是说出某物是否相交。我们可以从以下两个聚合开始:

    db.colors.aggregate([
        {'$unwind' : "$left"},
        {'$unwind' : "$right"},
        {'$project': {  
                        left:"$left",
                        right:'$right',
                        same:{$cond:[{$eq:["$left","$right"]}, 1, 0]}
                     }
        },
        {'$group'  : {
                        _id:{id:'$_id', left:'$left'},
                        right:{'$addToSet':'$right'},
                        sum: {'$sum':'$same'},
                     }
        },
        {'$project': {  
                        left:{val:"$_id.left",inter:"$sum"},
                        right:'$right',
                     }
        },
        {'$unwind' : "$right"},
        {'$project': {  
                        left:"$left",
                        right:'$right',
                        same:{$cond:[{$eq:["$left.val","$right"]}, 1, 0]}
                     }
        },
        {'$group'  : {
                        _id:{id:'$_id.id', right:'$right'},
                        left:{'$addToSet':'$left'},
                        sum: {'$sum':'$same'},
                     }
        },
        {'$project': {  
                        right:{val:"$_id.right",inter:"$sum"},
                        left:'$left',
                     }
        },
        {'$unwind' : "$left"},
        {'$group'  : {
                        _id:'$_id.id',
                        left:{'$addToSet':'$left'},
                        right: {'$addToSet':'$right'},
                     }
        },
    ]);
    

    对问题中提供的样本的聚合将给出如下结果:

    {
            "_id" : 1,
            "left" : [
                    {
                            "val" : "green",
                            "inter" : 1
                    },
                    {
                            "val" : "red",
                            "inter" : 0
                    }
            ],
            "right" : [
                    {
                            "val" : "blue",
                            "inter" : 0
                    },
                    {
                            "val" : "green",
                            "inter" : 1
                    }
            ]
    }
    

    从这里我们可以通过将以下内容添加到聚合中来获得交集:

    {'$project': {  
                        left:"$left"
                     }
        },
        {'$unwind' : "$left"},
        {'$match'  : {'left.inter': 1}},
        {'$group'  : {
                        _id:'$_id',
                        left:{'$addToSet':'$left'},
                     }
        },
    

    我们可以通过将以下内容添加到基本聚合的末尾来找到差异以及相对补充:

    {'$unwind' : "$left"},
        {'$match'  : {'left.inter': 0}},
        {'$unwind' : "$right"},
        {'$match'  : {'right.inter': 0}},
        {'$group'  : {
                        _id:'$_id',
                        left:{'$addToSet':'$left'},
                        right:{'$addToSet':'$right'},
                     }
        },
    

    不幸的是,似乎没有一种将来自不同领域的不同项目组合在一起的好方法。为了获得工会,似乎最好从客户那里做到这一点。或者,如果您想要过滤,请分别对每个集合进行过滤。

    【讨论】:

    • 认真的吗?我为您指出了“联合”的答案,为您提供了相对互补和交集的答案。真的需要对此提出另一个问题吗?
    • @Asya,这不是敲你的。我在上面的 cmets 中给了你信用。我添加这个的原因是其他那些是更具体的案例。第一个是您要解析出交叉点的位置。第二种情况你帮我找到了左补码。我认为另一个问题是有用的,因为其他问题非常特定于用例。我正在努力帮助社区,而不是从您发布的答案中拿走任何功劳。这个问题作为社区 wiki 类型更好地服务。你应该更开心,我在帮助你的公司并付钱。
    • 如果可能,这是我预先计算和存储的类型。我不想在生产系统中遇到这样的聚合管道。
    • 我同意,我希望使用它的用例代替了 $where。至少就我而言,我有一个为日常任务调整的模式。该模式不适合回答一些一次性问题。我似乎有理由尝试这个。我想计数器可能是如果你正在做临时并且实时性能并不重要,只需使用 $where 或 map reduce 就可以了。
    猜你喜欢
    • 2011-09-11
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 2014-05-27
    • 2018-06-14
    • 2018-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多