MongoDB入门三步曲2--基本操作(续)--聚合、索引、游标及mapReduce

mongodb 基本操作(续)--聚合、索引、游标及mapReduce

目录

聚合操作

像大多关系数据库一样,Mongodb也提供了聚合操作,这里仅列取常见到的几个聚合操作: Count计数
就像db.collection.find()操作能返回满足条件的记录一样,db.collection.count()返回满足条件的记录数,如下:

db.blog.count({"title":"mongo"})

此命令返回blog集合中title="mongo"的记录数。

Distinct去重 从其定义db.collection.distinct(keyword, condition)可看出,distinct是基于查询条件condition后对结果中的字段keyword去重的,如果不指定condition,则全表查询后对keyworld去重,示例如下:

db.blog.distinct("author")

此命令会把blog集合中不重复的author值返回出来。
有时在查询时想加个条件,如某天2014-02-26发表过文章的作者查询出来,例如下面的命令即可:

db.blog.distinct("author", {"create":"2014-02-26"})

当然还可以统计去重后的数量,因为返回的结果是个js的列表数据,所以只需要统计该列表的长度:

db.blog.disinct("author").length
或者
var authors = db.blog.distinct("author")
authors.length

group分组 使用过关系数据库如mysql的朋友应该知道,在mysql里group是对查询结果按某个字段分组重组后返回查询的数据列表,即返回的数据仍然是一条一条的,不改变原数据库表中的模式,只是对结果中各记录的前后顺序调整了下。
在mongodb中,分组也需要指定使用的字段,称为key,与mysql不同的地方在于需要指定具有相同key的数据存放位置,通常是一个数组,即对结果的initial;最后还要指定把哪些字段放到结果集里。举例如下:

db.blog.group({
  "key":{"author":true},
  "initial":{"blog":[]},
  "$reduce":function(doc,aggregation){
    aggregation.blog.push({"title":doc.title,"create":doc.create});
  },
  "condition":{"create":{$gte:"2014-01-01"}},
  "finalize":function(aggregation){
    aggregation.count=aggregation.blog.length;
  }
})

上面这条命令就根据author分组,并每条记录中的title和create字段放到分组聚合信息中。
在定义$reduce函数时,需要指定两个参数:

  • doc 当前操作的记录
  • aggregation 按key分组后的聚合信息
    对于$reduce,除了上面可以把查出来的信息添加进去还能执行很多其他操作,例如计算某个字段的统计值等。场景不同,方法也各异。
    group操作中的condition及finalize参数不是必须的,但在有时候也是非常有用的:
  • condition 对group操作提供条件过滤,如上只对create >= '2014-01-01'的记录进行分组统计
  • finalize 会在每组文档分组完成后触,如上命令中,在每个分组完成后统计各分组的数量,并输出到count字段。

MapReduce

MapReduce是分布式计算中的一种编程模型,其中指定一个map函数,用于映射每条数据到不同分组,reduce则对各组进行计算。因此在mongodb中,MapReduce也可以看作是聚合函数的一种复杂场景。
最简单的mapReduce调用需要三个参数:map,reduce,和结果被输出的集合

db.blog.mapReduce(
  function(){emit(this.author, {author:this.author,count:1});},
  function(key, values){
    var result = {"author":"", "count":0};
    for(var i = 0, i < values.length; i++) {
      result.count += values[i].count;
      result.author = values[i].author;
    }
    return result;
  },
  {"out":"authorStatistics"}
)

ok,执行完此语句,我们将获得一个新的集合authorStatistics,该集合有两个*字段:id和value,其中id的值为key对应的值,value有子json层,格式如result中定义的那般。

{ "_id" : "enjiex", "value" : { "author" : "enjiex", "count" : 1 } }
{ "_id" : "enjiex1", "value" : { "author" : "enjiex1", "count" : 2 } }

下面重点讲解下map及reduce函数:

  • map函数:在map函数中调用了emit函数,此函数有两个参数:key和value,其中key是对数据分组的分组字段,value则对应于需要参与计算的值串。例如上面程序中的值串是{name:this.author,count:1},对一条数据经过此方法调用后,就生成了一个中间值,类似这样:
    {"key":"enjiex", "value":{"author":"enjiex", "count":1}}
    

当对所有查询数据经过map方法调用后,会生成类似的中间结果:

[{"key":"enjiex",
"value":[
    {"author":"enjiex","count":1},
    {"author":"enjiex","count":1}
  ]
},
{"key":"mc",
"value":{"author":"enjiex","count":1}}
]

可以看出该中间结果是一个集合,是对emit中的value按key分组后的数据。

  • reduce函数:有两个参数:key和values,其中key的含义与emit中的含义相同,values则对应于map方法生成的中间结果。在reduce函数中对中间结果再做处理,如上程序中,只对中间结果中的count字段做了求和计算。
  • 结果输出:在mapReduce方法中最后一个参数:{"out":"authorStatistics"}指出了结果集的输出位置为:authorStatistics,执行完调用后会在当前库下生成一个名为authorStatistics的集合。

游标

游标概念
在mongo shell中执行db.blog.find(),时立即把结果输出到shell窗口中,但是如果我们把db.blog.find()赋给一个变量:

var blogs = db.blog.find()

那么blogs其实就拿到了mongodb返回的游标了。拿到游标后就可以做一些不一样的事情了,最简单的就是mongo shell中执行db.blog.find()效果一样的遍历输出--其实在shell中直接执行db.blog.find()也是返回了游标,只不过shell帮我们遍历输出了数据。

var blogs = db.blog.find()
while(blogs.hasNext()) {
  var blog = blogs.next();
  print(blog.title)
}

如上代码中,blogs是一个游标,对其调用blogs.hasNext()时,其默认会一次性从数据库中读取100条文档,然后调用blogs.next()时会返回当前位置的记录。
游标操作
除上可以对游标执行hasNext()及next()操作外,还可以对游标执行limit,skip及sort操作:

  • limit:限制游标返回的数量,即数据返回上限。
  • skip:指定返回结果中的前多少条数据被忽略,如果文档总数小于skip的数值,则返回空集合;
  • sort:对得到的集合按指定字段进行排序。
    因为在游标上执行以上操作返回的仍然是游标,所以可以在游标上按方法链调用:
    var blogs = db.blog.find();
    blogs.limit(100).skip(10),sort({"author":1,"create":1})

    上面代码就先限制返回数据量为100,再跳过前10条数据(实际有效的数据是90条了),然后再按author和create排序。
    其实上面的代码不是太有效,因为浪费了10条记录的返回,可能下面的写法会更好一些:

    db.blog.find().skip(10).limit(100).sort({"author":1,"create":1});
    

    这个实际返回的有效数据是100条了。

索引

索引基础
索引是提高数据查询效率的有效手段,通过在常用的字段或字段组合上建立索引,就能很多好提高在该字段或字段组合上的查询效率。简单创建索引的方法:

db.blog.ensuerIndex({"author":1})

通过上面方法,就为blog集合在author字段上建立了顺序索引(author的值分配到一棵B树上,同时维护到原数据记录的引用)。后续再通过author条件查询时,就会先在这棵索引树上查找,并返回最终数据记录。
可是,通过建立索引,查询效率真的提高了吗?在建立索引前后,可通过explain方法看到查询操作遍历的记录数:

db.blog.find({"author":"enjiex"}).explain()

创建完索引后,你会发现在相同的库下面会多出一个system.indexes集合,该集合存放了当前库中所有的索引创建信息。通过db.system.indexes.find()可查看当前库下有哪些索引记录。

唯一索引
假如在blog命令中,每个author只能发表一篇文章,也就是说每个author的值在blog记录中只能出现一次,通过建立唯一索引就可以了。

db.blog.ensuerIndex({"author":1}, {"unique":true})

注意,如果blog集合中已经出现了相同author的多条记录,上面的命令是不会执行成功的,只能先手动删除重复记录后再执行该命令才可以。

组合索引
回到唯一索引前的场景,每个author可能会发表很多篇文章,但我们需要经常查询某位author在某一天的文章。只要在author和create上建立个组合索引就能提高此种查询的效率。

db.blog.ensureIndex({"name":1, "birthday":1})

对于mongodb来说,{"name":1, "birthday":1}和{"birthday":1,"name":1}是不同的索引,因此在建立索引的时候,一定要结合实际查询场景,准确处理。
如果对于查询,不太确定是否使用了自己建立的索引,可以结合explain()查看查询细节。
删除索引
创建了这么多索引,但随着业务变化,某些索引已经不再需要了,如果继续维护会增加很多不必要的开销,对这些无效索引要及时予以清除。

db.blog.dropIndexes("index_name")

调用dropIndexes,指定要删除的索引名称即可。
前面一直没有说索引名称,一句话就能查询出来:

db.blog.getIndexes()
上一篇:IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试


下一篇:AttributeError: 'NoneType' object has no attribute 'split' 报错处理