mongo aggregation 查询操作

最近需要使用 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进行分页,不过如果要获取总的统计记录数目, 只能再行一次查询计算。

2016-03-15