最近需要使用 mongo 进行业务上的统计,自己摸索了一下,跟着问题,上直接上例子
一直进行数据上的统计常见的操作是多条具有相同 field 在不同时段产生的多条数据进行计算,
在 mongo 中可以借助于 aggregation framwork 来方便实现数据的统计;
比如说有如下几条记录:
> db.demo.find()
{ "_id" : ObjectId("56e6606b4824a645f55630b2"), "name" : "jia", "a" : 22, "b" : 11, "c" : 12 }
{ "_id" : ObjectId("56e6606d4824a645f55630b3"), "name" : "jia", "a" : 22, "b" : 11, "c" : 12 }
{ "_id" : ObjectId("56e660784824a645f55630b4"), "name" : "jia2", "a" : 20, "b" : 10, "c" : 10 }
{ "_id" : ObjectId("56e6607f4824a645f55630b5"), "name" : "jia2", "a" : 20, "b" : 10, "c" : 10 }
{ "_id" : ObjectId("56e6608e4824a645f55630b6"), "name" : "jia3", "a" : 10, "b" : 1, "c" : 1 }
{ "_id" : ObjectId("56e6608f4824a645f55630b7"), "name" : "jia3", "a" : 10, "b" : 1, "c" : 1 }
{ "_id" : ObjectId("56e76e9c4824a645f55630b8"), "name" : "jia3", "a" : 10, "b" : 1, "c" : 1 }
{ "_id" : ObjectId("56e76ea04824a645f55630b9"), "name" : "jia", "a" : 10, "b" : 1, "c" : 1 }
{ "_id" : ObjectId("56e771a04824a645f55630ba"), "name" : "jia0", "a" : 0, "b" : 1, "c" : 1 }
>
需要计算相同名字的 a 总值和 b 总值,并且还要计算 a/b 的比例大小,再加上排序和分页
官方有相关的文档,但是例子都个别说明,没有更深的组合使用说明,自己也是第一次接触这个 aggregation,
网上搜了一下,和请假社区的大牛,也终于解决这个问题。
一般都会先把 shell 查询语句弄清楚后再根据具体所用的开发进行 translation。
shell 命令如下:
>db.demo.aggregate([
{$group:{_id:"$name",
suma:{$sum:"$a"},
sumb:{$sum:"$b"}}},
{$project:{name:1,a:1,b:1,suma:1,sumb:1,_id:1,bdiva:{$cond:[{$eq:["$suma",0]},0,{$divide:["$sumb","$suma"]}]}}},
{$sort:{_id:-1}}
])
注意,这里$group
和$project
讲究顺序,不同的顺序有不同的效果
sort
可选字段为$project
中定义的所有字段
$group
里面的”_id”的值只能是非 primary key, 因为如果做表的 primary key 作为 group, 是没必要的。
项目使用 golang 开发,自然要用 go 实现,mgo 作为数据库驱动。
一开始困绕自己的是$eq
,$cond
这些平常不用的查询语句的 go 实现,比如$eq
这个在 shell 中使用了字符串和数字的组合,一下没反应过来怎么表示,
后来在同事的提醒使到了万能的 interface{}, 直接上 go 代码:
func AggregateQueryDemo() {
println("//<<-------------------------AggregateQueryDemo start-----------")
start := time.Now()
session, err := mgo.Dial(ADDRESSPORT)
if err != nil {
return
}
defer session.Close()
c := session.DB(DB).C(COLLECTION)
project := bson.M{
"$project": bson.M{
"name": true, "a": true, "b": true, "suma": true, "sumb": true, "_id": true,
"bdiva": bson.M{"$cond": []interface{}{bson.M{"$eq": []interface{}{"$suma", 0}}, 0, bson.M{"$divide": []interface{}{"$sumb", "$suma"}}}},
},
}
group := bson.M{
"$group": bson.M{
"_id": "$name",
"sumb": bson.M{"$sum": "$b"},
"suma": bson.M{"$sum": "$a"},
},
}
sort := bson.M{
"$sort": bson.M{
"bdiva": -1,
},
}
//order is important
operations := []bson.M{group, project, sort}
// operations := []bson.M{project, group, sort}
pipe := c.Pipe(operations)
ret := []interface {
}{}
err = pipe.All(&ret)
if err != nil {
panic(err.Error())
return
}
for k, v := range ret {
fmt.Printf("%+v: %+v\n", k, v)
}
fmt.Printf(" %v microseconds\n", time.Since(start)/1000000)
println("//---------------------------AggregateQueryDemo end----------->>")
}
可以加上$match
进行过滤查询,$skip
,$limit
进行分页,不过如果要获取总的统计记录数目, 只能再行一次查询计算。