2016-07-26 65 views
3

我有一些文件有一个数组属性项。 我想获得n篇文章之间的截距。几个数组的交集

db.things.insert({name:"A", items:[1,2,3,4,5]}) 
db.things.insert({name:"B", items:[2,4,6,8]}) 
db.things.insert({name:"C", items:[1,2]}) 
db.things.insert({name:"D", items:[5,6]}) 
db.things.insert({name:"E", items:[9,10]}) 
db.things.insert({name:"F", items:[1,5]}) 

数据:

{ "_id" : ObjectId("57974a0d356baff265710a1c"), "name" : "A", "items" : [ 1, 2, 3, 4, 5 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1d"), "name" : "B", "items" : [ 2, 4, 6, 8 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1e"), "name" : "C", "items" : [ 1, 2 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a1f"), "name" : "D", "items" : [ 5, 6 ] }, 
{ "_id" : ObjectId("57974a0d356baff265710a20"), "name" : "E", "items" : [ 9, 10 ] }, 
{ "_id" : ObjectId("57974a1a356baff265710a21"), "name" : "F", "items" : [ 1, 5 ] } 

例如: things.mane.A截距截距things.mane.C things.mane.F:

[1,2,3,4 ,5]截距[1,2]截距[1,5]

必须是:[1]

我认为这是使用$ setIntersection可行,但我找不到方法。

我可以做到两个文件,但如何做到更多?

db.things.aggregate({$match:{"name":{$in:["A", "F"]}}}, 
    {$group:{_id:null, "setA":{$first:"$items"}, "setF":{$last:"$items"} } }, 
    { 
      "$project": { 
       "set1": 1, 
       "set2": 1, 
       "commonToBoth": { "$setIntersection": [ "$setA", "$setF" ] }, 
       "_id": 0 
      } 
     } 
    ) 

{ "commonToBoth" : [ 5, 1 ] } 

回答

1

如果您使用的是蒙戈3.2,你可以使用arrayElemAt精确的$setIntersection所有参数:

db.things.aggregate([{ 
    $match: { 
     "name": { 
      $in: ["A", "B", "C"] 
     } 
    } 
}, { 
    $group: { 
     _id: 0, 
     elements: { 
      $push: "$items" 
     } 
    } 
}, { 
    $project: { 
     intersect: { 
      $setIntersection: [{ 
       "$arrayElemAt": ["$elements", 0] 
      }, { 
       "$arrayElemAt": ["$elements", 1] 
      }, { 
       "$arrayElemAt": ["$elements", 2] 
      }] 
     }, 
    } 
}]); 

你必须动态地添加需要的JSONObject的数量与指数如:

{ 
    "$arrayElemAt": ["$elements", <index>] 
} 

它应该与您的输入项的元素数相匹配["A", "B", "C"]

如果你要处理重复(一些name存在多个时间),通过name重组所有项目,$unwind两次$addToSet执行前一个聚合之前合并所有阵列特定$name

db.things.aggregate([{ 
    $match: { 
     "name": { 
      $in: ["A", "B", "C"] 
     } 
    } 
}, { 
    $group: { 
     _id: "$name", 
     "items": { 
      "$push": "$items" 
     } 
    } 
}, { 
    "$unwind": "$items" 
}, { 
    "$unwind": "$items" 
}, { 
    $group: { 
     _id: "$_id", 
     items: { 
      $addToSet: "$items" 
     } 
    } 
}, { 
    $group: { 
     _id: 0, 
     elements: { 
      $push: "$items" 
     } 
    } 
}, { 
    $project: { 
     intersect: { 
      $setIntersection: [{ 
       "$arrayElemAt": ["$elements", 0] 
      }, { 
       "$arrayElemAt": ["$elements", 1] 
      }, { 
       "$arrayElemAt": ["$elements", 2] 
      }] 
     }, 
    } 
}]); 

它不是一个干净的解决方案,但它的工作原理

1

一个解决方案,它是不特定的输入项的数目可能看起来像这样:

db.things.aggregate(
    { 
     $match: { 
      "name": { 
       $in: ["A", "F"] 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: "$items", 
      count: { 
       $sum: 1 
      } 
     } 
    }, 
    { 
     $group: { 
      _id: null, 
      totalCount: { 
       $sum: "$count" 
      }, 
      items: { 
       $push: "$_id" 
      } 
     } 
    }, 
    { 
     $unwind: { 
      path: "$items" 
     } 
    }, 
    { 
     $unwind: { 
      path: "$items" 
     } 
    }, 
    { 
     $group: { 
      _id: "$items", 
      totalCount: { 
       $first: "$totalCount" 
      },    
      count: { 
       $sum: 1 
      } 
     } 
    }, 
    { 
     $project: { 
      _id: 1, 
      presentInAllDocs: { 
       $eq: ["$totalCount", "$count"] 
      } 
     } 
    }, 
    { 
     $match: { 
      presentInAllDocs: true 
     } 
    }, 
    { 
     $group: { 
      _id: null, 
      items: { 
       $push: "$_id" 
      } 
     } 
    } 
) 

将输出这个

{ 
    "_id" : null, 
    "items" : [ 
     5, 
     1 
    ] 
} 

当然,你可以添加一个最后$project阶段带来的结果成所需的形状。


说明

这背后的基本理念是,当我们计算的文件的数量和我们计算每个项目的出现,然后用计数的项目等于总文档数量计数出现在每个文件中,因此处于相交结果中。
这个想法有一个重要的假设:你的items数组没有重复(即它们是集合)。如果这个假设是错误的,那么你将不得不在流水线的开始处插入一个额外的阶段来将数组转换为集合。
也可以用不同的或可能更短的方式构建此管道,但我尽量保持资源使用率尽可能低,因此可能不必要(从功能角度来看)阶段。例如,items阵列作为我的假设的第二阶段组是,与文档相比,有很多不同的值/数组,因此其余的管道必须使用初始文档计数的一小部分。然而,从功能的角度来看,我们只需要文档的总数,因此我们可以跳过这个阶段,只需要一个$group阶段对所有文档进行计数,并将它们推入数组中以供以后使用 - 这当然是一个很大的打击用于内存消耗,因为我们现在有一系列所有可能的文档。