MongoDB聚合查询

ps:以前都在iteye写博文,现在工作换了,转战前端,基本告别了java和python,看这边氛围还不错,就把那里的博客迁移过来了~~~

出于对性能的要求,公司希望把Mysql的数据迁移到MongoDB上,于是我开始学习Mongo的一些CRUD操作,由于第一次接触NoSQL,还是有点不习惯。

先吐个槽,公司的Mongo版本是2.6.4,而用的java驱动包版本是超级老物2.4版。当时一个“如何对分组后的文档进行筛选”这个需求头痛了很久,虽然shell命令下可以使用Aggregation很方便地解决,但是java驱动包从2.9.0版本才开始支持该特性,我至今没有找到不用Aggregation解决上述需求的办法。只能推荐公司升级驱动包版本,希望没有后续的兼容问题。

Mongo2.2版本后开始支持Aggregation Pipeline,而java驱动包从2.9.0版本才开始支持2.2的特性,2.9版本是12年发布的,mongodb在09年就出现了,似乎Mongo对java的开发者不怎么友好←_←

MongoDB目前提供了三个可以执行聚合操作的命令:aggregate、mapReduce、group。三者在性能和操作的优劣比较见官网提供的表格 Aggregation Commands Comparison,这里不再赘述细节。

aggregate、mapReduce、group原型及内部实现

我从官网总结出来了这三个函数的原型及底层封装的命令

函数名:db.collection.group()

函数原型:

1db.collection.group( 2    { 3        key, 4        reduce, 5        initial 6        [, keyf] 7        [, cond] 8        [, finalize] 9    } 10)

封装的命令:

1db.runCommand( 2    { 3      group: 4       { 5        ns: <namespace>, 6        key: <key>, 7        $reduce: <reduce function>, 8        $keyf: <key function>, 9        cond: <query>, 10        finalize: <finalize function> 11       } 12    } 13)

函数名:db.collection.mapReduce()

函数原型:

1db.collection.mapReduce( 2    <map>, 3    <reduce>, 4    { 5        out: <collection>, 6        query: <document>, 7        sort: <document>, 8        limit: <number>, 9        finalize: <function>, 10        scope: <document>, 11        jsMode: <boolean>, 12        verbose: <boolean> 13    } 14)

封装的命令:

1db.runCommand( 2    { 3    mapReduce: <collection>, 4    map: <function>, 5    reduce: <function>, 6    finalize: <function>, 7    out: <output>, 8    query: <document>, 9    sort: <document>, 10    limit: <number>, 11    scope: <document>, 12    jsMode: <boolean>, 13    verbose: <boolean> 14    } 15)

函数名:db.collection.aggregate()

函数原型:

1db.collection.aggregate( 2    pipeline, 3    options 4)

封装的命令:

1db.runCommand( 2    { 3      aggregate: "<collection>", 4      pipeline: [ <stage>, <...> ], 5      explain: <boolean>, 6      allowDiskUse: <boolean>, 7      cursor: <document> 8    } 9)

Mysql与MongoDB对聚合处理的对比

好记性不如烂笔头,下面通过操作来了解这几个函数和命令

1、准备测试数据

先准备SQL的测试数据(用来验证结果、比较SQL语句和NoSQL的异同):

先创建数据库表:

1create table dogroup (   2       _id int,   3       name varchar(45),   4       course varchar(45),   5       score int,   6       gender int,   7       primary key(_id)   8);

 插入数据:

1insert into dogroup (_id, name, course, score, gender) values (1, "N", "C", 5, 0);   2insert into dogroup (_id, name, course, score, gender) values (2, "N", "O", 4, 0);   3insert into dogroup (_id, name, course, score, gender) values (3, "A", "C", 5, 1);   4insert into dogroup (_id, name, course, score, gender) values (4, "A", "O", 6, 1);   5insert into dogroup (_id, name, course, score, gender) values (5, "A", "U", 8, 1);   6insert into dogroup (_id, name, course, score, gender) values (6, "A", "R", 8, 1);   7insert into dogroup (_id, name, course, score, gender) values (7, "A", "S", 7, 1);   8insert into dogroup (_id, name, course, score, gender) values (8, "M", "C", 4, 0);   9insert into dogroup (_id, name, course, score, gender) values (9, "M", "U", 7, 0);   10insert into dogroup (_id, name, course, score, gender) values (10, "E", "C", 7, 1);

接着准备MongoDB测试数据:

创建Collection(等同于SQL中的表,该行可以不写,Mongo会在插入数据时自动创建Collection)

db.createCollection("dogroup")

插入数据:

1db.dogroup.insert({"_id": 1,"name": "N",course: "C","score": 5,gender: 0}) 2db.dogroup.insert({"_id": 2,"name": "N",course: "O","score": 4,gender: 0}) 3db.dogroup.insert({"_id": 3,"name": "A",course: "C","score": 5,gender: 1}) 4db.dogroup.insert({"_id": 4,"name": "A",course: "O","score": 6,gender: 1}) 5db.dogroup.insert({"_id": 5,"name": "A",course: "U","score": 8,gender: 1}) 6db.dogroup.insert({"_id": 6,"name": "A",course: "R","score": 8,gender: 1}) 7db.dogroup.insert({"_id": 7,"name": "A",course: "S","score": 7,gender: 1}) 8db.dogroup.insert({"_id": 8,"name": "M",course: "C","score": 4,gender: 0}) 9db.dogroup.insert({"_id": 9,"name": "M",course: "U","score": 7,gender: 0}) 10db.dogroup.insert({"_id": 10,"name": "E",course: "C","score": 7,gender: 1})

以下操作可能逻辑上没有实际意义,主要是帮助熟悉指令

2、查询每门课程参与考试的人数

SQL写法:

select course as '课程名', count(*) as '数量' from dogroup group by course;

MongoDB写法:

① group方式

1db.dogroup.group({   2key : { course: 1 },   3initial : { count: 0 },   4reduce : function Reduce(curr, result) {   5    result.count += 1;   6},   7finalize : function Finalize(out) {   8    return {"课程名": out.course, "数量": out.count};   9}   10});

 返回的格式如下:

1{   2        "课程名" : "C",   3        "数量" : 4   4},   5{   6        "课程名" : "O",   7        "数量" : 2   8},   9{   10        "课程名" : "U",   11        "数量" : 2   12},   13{   14        "课程名" : "R",   15        "数量" : 1   16},   17{   18        "课程名" : "S",   19        "数量" : 1   20}

② mapReduce方式

1db.dogroup.mapReduce(   2    function () {   3        emit(   4            this.course,   5            {course: this.course, count: 1}   6        );   7    },   8    function (key, values) {   9        var count = 0;   10        values.forEach(function(val) {   11            count += val.count;   12        });   13        return {course: key, count: count};   14    },   15    {   16        out: { inline : 1 },   17        finalize: function (key, reduced) {   18            return {"课程名": reduced.course, "数量": reduced.count};   19        }   20    }   21)

这里把count初始化为1的原因是,MongoDB执行完map函数(第一个函数)后,如果key所对应的values数组的元素个数只有一个,reduce函数(第二个函数)将不会被调用。

返回的格式如下:

1{   2      "_id" : "C",   3      "value" : {   4              "课程名" : "C",   5              "数量" : 4   6      }   7},   8{   9      "_id" : "O",   10      "value" : {   11              "课程名" : "O",   12              "数量" : 2   13      }   14},   15{   16      "_id" : "R",   17      "value" : {   18              "课程名" : "R",   19              "数量" : 1   20      }   21},   22{   23      "_id" : "S",   24      "value" : {   25              "课程名" : "S",   26              "数量" : 1   27      }   28},   29{   30      "_id" : "U",   31      "value" : {   32              "课程名" : "U",   33              "数量" : 2   34      }   35}

③ aggregate方式

1db.dogroup.aggregate(   2    {   3        $group:   4        {   5            _id: "$course",   6            "数量": { $sum: 1 }   7        }   8    }   9)

返回格式如下:

1{ "_id" : "S", "数量" : 1 }   2{ "_id" : "R", "数量" : 1 }   3{ "_id" : "U", "数量" : 2 }   4{ "_id" : "O", "数量" : 2 }   5{ "_id" : "C", "数量" : 4 }

以上三种方式中,group得到了我们想要的结果,mapReduce返回的结果只能嵌套在values里面,aggregate必须返回_id,无法为分组的字段指定别名,但是无疑第三种是最简单的。

虽然上面的问题不影响程序在前台展现数据,但是对于一个略微有强迫症的开发者确实难以忍受的。本人才疏学浅,刚接触Mongo,不知道后两者有没有可行的方法来获取想要的结果,希望网友指教。

3、查询Docouments(等同于SQL中记录)数大于2的课程

SQL写法:

select course, count(*) as count from dogroup group by course having count > 2;

MongoDB写法:

① aggregate方式(注意$group和$match的先后顺序)

1db.dogroup.aggregate({   2    $group: {   3        _id: "$course",   4        count: { $sum: 1 }   5    }   6    },{   7    $match: {   8        count:{   9            $gt: 2   10        }   11    }   12});

 目前尚未找到group和mapReduce对分组结果进行筛选的方法,欢迎网友补充

4、找出所有分数高于5分的考生数量及分数,返回的格式为“分数、数量”

SQL写法:

select score as '分数', count(distinct(name)) as '数量' from dogroup where score > 5 group by score;

MongoDB写法:

① group方式

1db.dogroup.group({   2    key : { score: 1 },   3    cond : { score: {$gt: 5} },   4    initial : { name:[] },   5    reduce : function Reduce(curr, result) {   6        var flag = true;   7        for(i=0;i<result.name.length&&flag;i++){   8            if(curr.name==result.name[i]){   9                flag = false;   10            }   11        }   12        // 如果result.name数组里面没有curr.name则添加curr.name   13        if(flag){   14            result.name.push(curr.name);   15        }   16    },   17    finalize : function Finalize(out) {   18        return {"分数": out.score, "数量": out.name.length};   19    }   20});

② mapReduce方式

1db.dogroup.mapReduce(   2    function () {   3        if(this.score > 5){   4            emit(   5                this.score,   6                {score: this.score, name: this.name}   7            );   8        }   9    },   10    function (key, values) {   11        var reduced = {score: key, names: []};   12        var json = {};//利用json对象的key去重   13        for(= 0; i < values.length; i++){   14            if(!json[values[i].name]){   15                reduced.names.push(values[i].name);   16                json[values[i].name] = 1;   17            }   18        }   19        return reduced;   20    },   21    {   22        out: { inline : 1 },   23        finalize: function (key, reduced) {   24            return {"分数": reduced.score, "数量": reduced.names?reduced.names.length:1};   25        }   26    }   27)

③ aggregate方式

1db.dogroup.aggregate({   2        $match: {   3            score: {   4                $gt: 5   5            }   6        }   7    },{   8        $group: {   9            _id: {   10                score: "$score",   11                name: "$name"   12            }   13        }   14    },{   15        $group: {   16            _id: {   17                "分数": "$_id.score"   18            },   19            "数量": { $sum: 1 }   20        }   21});

弄熟上面这几个方法,大部分的分组应用场景应该没大问题了。

这张图示可以更直观地理解(点击看大图):

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

MongoDB聚合查询 - HelloWorld