zoukankan      html  css  js  c++  java
  • 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() 
    
    || 一个理想主义者
  • 相关阅读:
    Intern Day7
    Intern Day7
    Intern Day7
    Intern Day6
    Intern Day6
    Intern Day6
    Intern Day6
    Intern Day6
    萧萧远树疏林外,一半秋山带夕阳
    飞线
  • 原文地址:https://www.cnblogs.com/enjiex/p/3574496.html
Copyright © 2011-2022 走看看