正如江湖传言,mongodb是nosql数据库中对关系查询支持最好的。这里介绍其用python语言的groupby操作(用pymongo实现)。
首先说一下这项工作的背景,线上有一个mongo数据库,名为test。其中,有两个我们要关心的集合,一个是paper_info,另一个是aff_info,下面分别介绍:
paper_info中存放的是一些论文信息,比如论文的Authors,会议或期刊名称Journal,还有期刊的分级Rank等。下面该集合中的一个doc,可以作为一个例子。
{ _id: ObjectId("5242fd5e02387746099f9cc5"), Rank: "unknow", Title: "Global Convergence of Inexact Reduced SQP Methods", Journal: "Universität Trier, Mathematik/Informatik, Forschungsbericht", Publtype: "informal publication", Year: "1995", Volume: "95-20", Key: "tr/trier/MI95-20", Authors: [ "Heidi Jäger", "Ekkehard W. Sachs" ], Authors_Low_Case: [ "heidi jäger", "ekkehard w. sachs" ], Type: "article", Id: "62" }aff_info中存放的是一些单位的信息,其中包含属于该单位的学者列表。下面该集合中的一个doc,可以作为例子理解。
{ _id: ObjectId("523efb2b02387725a9fdf38b"), Stat:"A", Scholars: [ "Hyong-Jun La", "Thura Lin Naing", "Akshay Krishnamurthy", "J.-S. Roger", "Tracy Xiaoxiao Wang", "Hoi-Sheung Wilson", "Jeffrey Herman", "Kenneth Keller" ], Id: 1794, Name: "University of California" }下面,根据上面两个库中的信息,我们想统计一下各个单位中的论文发表情况,简单的说就是各单位每年发了各类文章各多少篇。这里要用到mongodb的group操作,也就是传统的关系数据库中的group by。
pymongodb的官方文档中,对于group是这样说明的。
The group() method provides some of the same functionality as SQL’s GROUP BY. Simpler than a map reduce you need to provide a key to group by, an initial value for the aggregation and a reduce function.
Note:Doesn’t work with sharded MongoDB configurations, use aggregation or map/reduce instead of group().
Here we are doing a simple group and count of the occurrences x values:
>>> reducer = Code(""" ... function(obj, prev){ ... prev.count++; ... } ... """) >>> from bson.son import SON >>> results = db.things.group(key={"x":1}, condition={}, initial={"count": 0}, reduce=reducer) >>> for doc in results: ... print doc {u'count': 1.0, u'x': 1.0} {u'count': 2.0, u'x': 2.0} {u'count': 1.0, u'x': 3.0}实际上,简单说,group函数group(key,condition,initial,reduce)完成的功能是:对集合中所有符合condition条件的记录,根据key指定的字段进行分组(也就是关系数据库中的group by)。对于一组中的各个记录,都作为reduce函数的第一个参数执行reduce函数。最后的结果是一个数组,其中的每一个元素是根据一组数据产生一条记录,也就是doc(也可以理解成字典吧!?)。该记录中包含key参数中指定的字段和initial中指定的字段。为了理解group,还要理解reduce函数的第二个参数,它可以理解成是对initial中指定的字段的引用,对每一组内的所有记录可见。到这里就不难理解上面的例子中为什么可以对prev.count++了吧?!
#!/usr/bin/python #-*-coding:utf-8-*- import pymongo import string def compute_stat(): con=pymongo.Connection("10.77.20.xx",27017) test=con.test paper_info=test.paper_info affiliation=test.aff_info aff_cursor=affiliation.find() for d in aff_cursor: schs=d["scholars"] func=''' function(obj,prev) { prev.count++; } ''' stat=paper_info.group({"Year":-1,"Rank":1},{"Authors":{"$in":schs}},{"count":0},func) def paixu(s): return string.atoi(s["Year"]) stat_sorted=sorted(stat,reverse=True,key=paixu) d["Stat"]=stat_sorted affiliation.save(d) return True
最后,关于代码中的两点还是要废话一下:
(1)group函数中的condition参数{"Authors":{"$in":schs}}:这里的Authors字段的值是一个数组,schs也是一个数组。这一个条件选中的实际是Authors数组中的任何一个元素出现在schs中的记录。
(2)排序,关于sorted这里传递了一个函数参数,详见代码。