正如江湖传言,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++了吧?!
下面的源码是将论文的统计信息更新到aff_info集合中的Stat字段:
#!/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这里传递了一个函数参数,详见代码。