我在mongodb中有一个非常大的项目集合,其架构是我无法改变的.简化版本如下所示:
{event: { address: {ip: "1.1.1.1", port: 80}}} {event: { address: {ip: "1.1.1.2", port: 80}}} {event: { address: [{ip: "1.1.1.1", port: 80}, {ip: "1.1.1.1", port: 443}]}} {event: { address: [{ip: "1.1.1.1", port: 8080}, {ip: "1.1.1.2", port: 443}]}}
每个事件可能有一个或多个地址.每个地址都有"ip"和"port".因此,在具有多个地址的事件中可能会重复"ip".
我想要做的就是计算每个IP地址的事件数量并找到最高IP地址.对于上面的示例,首选结果是:
[ { "ip" : "1.1.1.1", "count" : 3 }, { "ip" : "1.1.1.2", "count" : 2 } ]
想到的一个问题是这样的:
db.collection.aggregate({$project: {ip: "$event.address.ip"}}, {$group: {_id: "$ip", count: {$sum: 1}}}, {$sort: {count: -1}}, {$limit: 5})
但结果是:
{ "result" : [ { "_id" : ["1.1.1.1", "1.1.1.2"], "count" : 1 }, { "_id" : ["1.1.1.1", "1.1.1.1"], "count" : 1 }, { "_id" : "1.1.1.2", "count" : 1 }, { "_id" : "1.1.1.1", "count" : 1 } ], "ok" : 1 }
我不能使用$ unwind因为每个IP地址对于每个事件应该只计算一次,但是一些事件具有相同的IP重复.另外,$ unwind一般不起作用,因为"地址"并不总是一个数组.某些事件只有一个不是数组的地址,$ unwind会为它们抛出异常.
我尝试了$ group中的$ addToSet等不同的聚合运算符,但都无济于事.
该集合非常大,我不能首先提取我的应用程序中的所有IP地址,然后计算每个地址的事件.
可以使用map/reduce完成.你会建议什么?
虽然可以使用MapReduce完成,但聚合框架会更快.你需要为你的计划添加两个步骤 - 1)你需要"规范化"格式,以便地址始终是一个数组,2)然后你需要$展开该数组,按_id,ip分组以摆脱重复和然后通过ip分组以获得您需要的计数.
规范化数组和非数组是很棘手的,但可以使用前后两个投影来完成$unwind
.
var p1 = { "$project" : { "array" : { "$cond" : [ { "$eq" : [ "$address.0", [ ] ] }, "$address", [ null ] ] }, "notarray" : { "$cond" : [ { "$ne" : [ "$address.0", [ ] ] }, "$address", [ null ] ] }, "isArray" : { "$eq" : [ "$address.0.ip", [ ] ] } } }; var u = { "$unwind" : "$array" }; var p2 = { "$project" : { "address" : { "$cond" : [ "$isArray", "$array", "$notarray" ] } } };
相比之下,这两个$group
阶段很简单:
var g1 = { "$group" : { "_id" : { "_id" : "$_id", "ip" : "$address.ip" } } }; var g2 = { "$group" : { "_id" : "$_id.ip", "count" : { "$sum" : 1 } } };
这是我的示例数据:
> db.coll.find() { "_id" : ObjectId("52cd0badba17f3b7ed212575"), "address" : { "ip" : "1.1.1.1" } } { "_id" : ObjectId("52cd0bc4ba17f3b7ed212576"), "address" : [ { "ip" : "1.1.1.1" }, { "ip" : "1.1.1.1" } ] } { "_id" : ObjectId("52cd0bc9ba17f3b7ed212577"), "address" : [ { "ip" : "1.1.1.1" }, { "ip" : "1.1.1.2" } ] }
这是聚合及其输出:
> db.coll.aggregate(p1, u, p2, g1, g2) { "_id" : "1.1.1.1", "count" : 3 } { "_id" : "1.1.1.2", "count" : 1 }