使用MongoDB聚合在同一文档中查找两组的集合交集

时间:2013-06-23 18:38:13

标签: mongodb aggregation-framework set-intersection

我正在尝试使用Mongo聚合框架来查找在同一文档中有不同唯一集的记录的位置。一个例子将最好地解释这一点:

这是一份不是我的真实数据的文件,但在概念上是相同的:

db.house.insert(
{
    houseId : 123,
    rooms: [{ name : 'bedroom',
          owns : [
            {name : 'bed'},
            {name : 'cabinet'}
        ]},
        { name : 'kitchen',
          owns : [
            {name : 'sink'},
            {name : 'cabinet'}
        ]}],
       uses : [{name : 'sink'},
               {name : 'cabinet'},
               {name : 'bed'},
               {name : 'sofa'}]
}
)

请注意,有两个层次结构具有相似的项目。也可以使用非拥有的物品。我想找到像这样的文件:哪里有一个房子使用它不拥有的东西。

到目前为止,我已经使用如下的聚合框架构建了结构。这让我得到了2套不同的物品。但是我找不到任何可以给我一个交集的结果的东西。请注意,设置大小的简单计数将不起作用,因为这样的事情:['couch','cabinet']与['sofa','cabinet']比较。

{'$unwind':'$uses'}
{'$unwind':'$rooms'}
{'$unwind':'$rooms.owns'}
{'$group' : {_id:'$houseId', 
             use:{'$addToSet':'$uses.name'}, 
             own:{'$addToSet':'$rooms.owns.name'}}}

产生

{ _id : 123,
  use : ['sink', 'cabinet', 'bed', 'sofa'],
  own : ['bed', 'cabinet', 'sink']
}

如何在管道的下一阶段找到use和own的集合交集?

3 个答案:

答案 0 :(得分:4)

你离聚合框架的完整解决方案还不是很远 - 你需要在$group步骤之前再做一件事情,这样可以让你看看所有正在使用的东西是否与之匹配拥有的东西。

这是完整的管道

> db.house.aggregate(
       {'$unwind':'$uses'}, 
       {'$unwind':'$rooms'}, 
       {'$unwind':'$rooms.owns'}, 
       {$project:  { _id:0, 
                     houseId:1, 
                     uses:"$uses.name", 
                     isOkay:{$cond:[{$eq:["$uses.name","$rooms.owns.name"]}, 1, 0]}
                   }
       }, 
       {$group: { _id:{house:"$houseId",item:"$uses"}, 
                  hasWhatHeUses:{$sum:"$isOkay"}
                }
       },
       {$match:{hasWhatHeUses:0}})

及其在您的文档上的输出

{
    "result" : [
        {
            "_id" : {
                "house" : 123,
                "item" : "sofa"
            },
            "hasWhatHeUses" : 0
        }
    ],
    "ok" : 1
}

解释 - 一旦你打开两个数组,你现在想要标记使用的项目等于拥有项目的元素,并给它们一个非0“分数”。现在当你通过houseId重新组合时,你可以检查是否有任何使用过的物品没有得到匹配。使用1和0作为分数允许您进行总和,现在匹配项目,其中总和0表示它已被使用但与“拥有”中的任何内容都不匹配。希望你喜欢这个!

答案 1 :(得分:1)

所以这是一个不使用聚合框架的解决方案。这使用$ where运算符和javascript。这对我来说感觉更加笨拙,但似乎有用,所以如果有其他人遇到这个问题,我想把它放在那里。

db.houses.find({'$where': 
    function() { 
        var ownSet = {};
        var useSet = {};
        for (var i=0;i<obj.uses.length;i++){ 
            useSet[obj.uses[i].name] = true;
        }
        for (var i=0;i<obj.rooms.length;i++){ 
            var room = obj.rooms[i];
            for (var j=0;j<room.owns.length;j++){
                ownSet[room.owns[j].name] = true;
            }
        }
        for (var prop in ownSet) {
            if (ownSet.hasOwnProperty(prop)) {
                if (!useSet[prop]){
                    return true;
                }
            }
        }
        for (var prop in useSet) {
            if (useSet.hasOwnProperty(prop)) {
                if (!ownSet[prop]){
                    return true;
                }
            }
        }
        return false
    }
})

答案 2 :(得分:1)

仅适用于MongoDB 2.6+

从MongoDB 2.6开始,项目管道阶段就有可用的设置操作。使用新操作回答此问题的方法是:

db.house.aggregate([
    {'$unwind':'$uses'},
    {'$unwind':'$rooms'},
    {'$unwind':'$rooms.owns'},
    {'$group' : {_id:'$houseId', 
             use:{'$addToSet':'$uses.name'}, 
             own:{'$addToSet':'$rooms.owns.name'}}},
    {'$project': {int:{$setIntersection:["$use","$own"]}}}
]);