MongoDB 分组统计

MongoDB 在进行分组统计时如果面对一些比较复杂的计算情况,往往会遇到 shell 脚本过于复杂的问题。而集算器 SPL 语言,则因其有丰富的函数库及易用性恰好能弥补 Mongo 这方面的不足。

 MongoDB 作为 NoSql 文档型数据库,在全球范围得到广泛的支持与应用。在比较常用的数据库功能中,相对于普通的增删改查,使用 group 聚合分组统计有些复杂,而 MongoDB 也给予了支持。本文将对MongoDb分组的实现方法及示例进行分析,通过在 MongoDB 脚本中操作、使用集算器 SPL 语言操作两种操作途径,进行简单的归纳总结。具体的问题场景包括以下几个方面:

1. 内嵌数组结构的统计........................................................................... 1
2. 内嵌文档求和..................................................................................... 2
3. 分段分组结构统计.............................................................................. 4
4. 多字段分组统计................................................................................. 6  

1. 内嵌数组结构的统计

对嵌套数组结构中的数据进行统计处理例如查询考试科目的平均分及每个学生的总成绩:

测试数据:

_idnamesexScroe
1TomF[{"lesson":" Physics","mark":60   },
  {"lesson":" Chemical","mark":72 }]
2JerryM[{"lesson":" Physics","mark":92   },
  {"lesson":" Math","mark":81 }]

期待统计结果:

Physics76
Tom132
Chemical72
Jerry173
Math81


Mongodb脚本:

db.student.aggregate( [
  {$unwind : "$scroe"},
{$group: {
  "_id":   {"lesson":"$scroe.lesson"} ,
  "qty":{"$avg": "$scroe.mark"}
  }
}
] )

db.student.aggregate( [
  {$unwind : "$scroe"},
{$group: {
  "_id": {"name"   :"$name"} ,
  "qty":{"$sum" :   "$scroe.mark"}
  }
}
 ] )

由于各科分数 scroe 是按课目、成绩记录的数组结构,统计前需要将它拆解,将每科成绩与学生对应,然后再实现分组计算。这需要熟悉 unwind 与 group 组合的应用。


SPL 脚本 (student.dfx):


AB
1=mongo_open("mongodb://127.0.0.1:27017/raqdb")
2=mongo_shell(A1,"student.find()").fetch()
3=A2.conj(scroe).groups(lesson:LESSON;avg(mark):AVG)
4=A2.new(name:NAME,scroe.sum(mark):TOTAL)
5>A1.close()

按课目统计的总分数:

LESSONAVG
Chemical72.0
Math81.0
Physics76.0

每个学生的总成绩:

NAMETOTAL
Tom132
Jerry173


脚本说明:
       A1:连接 mongodb 数据库。
       A2:获取 student 表中的数据。
       A3:将 scroe 数据合并成序表,再按课程分组,计算平均分。
       A4:统计每个学生的成绩后返回列名为 NAME、TOTAL 的序表。new 函数表示生成新序表。
       A5:关闭数据库连接。


       这个嵌套结构统计的例子比较常见,相信很多人都遇到过,需要先拆解再分组计算,主要是熟悉 mongodb 对嵌套数据结构的处理。

2. 内嵌文档求和

对内嵌文档中的数据求和处理, 例如统计下面每条记录中 income,output 的数量和。
测试数据:

_idincomeoutput
1{"cpu":1000, "mem":500,   "mouse":"100"}{"cpu":1000, "mem":600 ,"mouse":"120"}
2{"cpu":2000, "mem":1000,
  "mouse":"50","mainboard":500 }
{"cpu":1500, "mem":300}


期待统计结果:

_id

income

output

1

1600

1720

2

3550

1800

Mongodb脚本:

var fields = [  "income", "output"];
db.computer.aggregate([ 
   { 
      $project:{ 
         "values":{ 
            $filter:{ 
               input:{ 
                    "$objectToArray":"$$ROOT"
               },
               cond:{ 
                  $in:[ 
                     "$$this.k",
                     fields
                  ]
               }
            }
         }
      }
   },
   { 
      $unwind:"$values"
   },
   { 
      $project:{ 
         key:"$values.k",
         values:{ 
            "$sum":{ 
               "$let":{ 
                  "vars":{ 
                     "item":{ 
                          "$objectToArray":"$values.v"
                     }
                  },
                    "in":"$$item.v"
               }
            }
         }
      }
   },
   {$sort: {"_id":-1}},
   { "$group": {
    "_id": "$_id",
    'income':{"$first":   "$values"},
    "output":{"$last":   "$values"}
    }},
]);

filter将income,output 部分信息存放到数组中,用 unwind 拆解成记录,再累计各项值求和,按 _id 分组合并数据。


SPL脚本:


AB
1=mongo_open("mongodb://127.0.0.1:27017/raqdb")
2=mongo_shell(A1,"computer.find()").fetch()
3=A2.new(_id:ID,income.array().sum():INCOME,output.array().sum():OUTPUT)
4>A1.close()

统计结果

IDINCOMEOUTPUT
11600.01720.0
23550.01800.0

脚本说明:
      A1:连接数据库
      A2:获取 computer 表中的数据
      A3:将 income、output 字段中的数据分别转换成序列求和,再与 ID 组合生成新序表
      A4:关闭数据库连接。


      获取子记录的字段值,然后求和,相对于 mongo 脚本简化了不少。这个内嵌文档与内嵌数组在组织结构上有点类似,不小心容易混淆,因此需要特别注意与上例中的 scroe 数组结构比较,写出的脚本有所不同。

3. 分段分组结构统计

统计各段内的记录数量。例如下面按销售量分段,统计各段内的数据量,数据如下:

_idNAMESTATESALES
1AshleyNew York11000
2RachelMontana9000
3EmilyNew York8800
4MatthewTexas8000
5AlexisIllinois14000


分段方法:0-3000;3000-5000;5000-7500;7500-10000;10000 以上。

期望结果:

Segmentnumber
33
42

Mongo 脚本

var a_count=0;
var b_count=0;
var c_count=0;
var d_count=0;
var e_count=0;
db.sales.find({
}).forEach(
    function(myDoc) {
        if (myDoc.SALES <3000)   {
            a_count += 1;
        }
        else if (myDoc.SALES <5000)   {
            b_count += 1;
        }
        else if (myDoc.SALES   <7500) {
            c_count += 1;
        }
        else if (myDoc.SALES   <10000) {
            d_count += 1;
        }
        else {
            e_count += 1;
        }       
    }
    );
   
print("a_count="+a_count)
print("b_count="+b_count)
print("c_count="+c_count)
print("d_count="+d_count)
print("e_count="+e_count)

这个需求按条件分段分组,mongodb 没有提供对应的 api,实现起来有点繁琐,上面的程序是其中实现的一个例子参考,当然也可以写成其它实现形式。下面看看集算器脚本的实现。

SPL脚本:


AB
1[3000,5000,7500,10000,15000]
2=mongo_open("mongodb://127.0.0.1:27017/raqdb")
3=mongo_shell(A2,"sales.find()").fetch()
4=A3.groups(A1.pseg(int(~.SALES)):Segment;count(1):   number)
5>A2.close()


脚本说明:
       A1:定义 SALES 分组区间。
       A2:连接 mongodb 数据库。
       A3:获取 sales 表中的数据。
       A4:根据 SALES 区间分组统计员工数。其中函数 pseg()表示返回成员在序列中的区段序号,int() 表示转换成整数。
       A5:关闭数据库连接。


       Mongodb脚本与 SPL 脚本都实现了预期的结果,但函数pseg 的使用让 SPL 脚本精简了不少。

4. 多字段分组统计

统计分类项下的总数及各子项数。下面统计按 addr 分类的 book 的数量以及其下不同 book 类型的数量。

addrbook
address1book1
address2book1
address1book5
address3book9
address2book5
address2book1
address1book1
address15book1
address4book3
address5book1
address7book11
address1book1


期望结果:

_idTotalbooksCount
address14book13


book51
address151book11
address23book12


book51
address31book91
address41book31
address51book11
address71book111


Mongo脚本

db.books.aggregate([
    {   "$group": {
          "_id": {
              "addr": "$addr",
              "book": "$book"
        },
          "bookCount": {"$sum": 1}
    }},
    {   "$group": {
          "_id": "$_id.addr",
          "books": {
              "$push": {
                  "book": "$_id.book",
                  "count": "$bookCount"
            },
        },
          "count": {"$sum": "$bookCount"}
    }},
    {"$sort":   { "count": -1} },
    {   "$project": {
          "books": {"$slice": [ "$books", 2] },
          "count": 1
    }}
]).pretty()

先按 addr,book 分组统计 book 数,再按 addr 分组统计 book 数,调整显示顺序。


SPL脚本 (books.dfx):


AB
1=mongo_open("mongodb://127.0.0.1:27017/raqdb")
2=mongo_shell(A1,"books.find()")
3=A2.groups(addr,book;count(book):   Count)
4=A3.groups(addr;sum(Count):Total)
5=A3.join(addr,A4:addr,Total)return A5
6>A1.close()


计算结果:

AddressbookCountTotal
address1book134
address1book514
address15book111
address2book123
address2book513
address3book911
address4book311
address5book111
address7book1111


脚本说明:
        A1:连接 mongodb 数据库。
        A2:获取 books 表中的数据。
        A3:按 addr,book 分组统计 book 数顾。
        A4:再按 addr 分组统计 book 数。
        A5:将 A4 中的 Total 按 addr 关联后合并到序表中。
        B5: 返回序表 A5。
        A6:关闭数据库连接。


        这个例子中的 SPL 脚本除了一如既往的精简清晰外,还显示了如何简单方便地与 Java 程序集成。

        在 Java 程序中如果要对 MongoDB 实现上面的分组统计功能,需要根据不同的需求重新一五一十地实现,比较麻烦的同时也不通用。而如果用集算器来实现就容易多了,集算器提供了 JDBC 驱动程序,支持在 Java 程序中用 JDBC 存储过程方式访问计算结果,调用方法与调用存储过程相同。(JDBC 具体配置参考《集算器教程》中的“JDBC基本使用”章节)
       Java 调用主要过程如下:
       public void testStudent (){
              Connection con = null;
              com.esproc.jdbc.InternalCStatement st;
       try{
             // 建立连接
             Class.forName("com.esproc.jdbc.InternalDriver");
             con= DriverManager.getConnection("jdbc:esproc:local://");
             //调用存储过程,其中books是 dfx 的文件名
             st =(com. esproc.jdbc.InternalCStatement)con.prepareCall("call books ()");
             //执行存储过程
             st.execute();
             // 获取结果集
             ResultSet rs = st.getResultSet();
              。。。。。。。
       catch(Exception e){
             System.out.println(e);
       }

       可以看到,集算器的计算结果能够很方便地供 Java 应用程序使用。除了上面的调用方式,程序也可以修改成直接加载 SPL 脚本的函数,用 SPL 脚本文件名当参数来实现。同时,集算器也支持 ODBC 驱动,与其它支持 ODBC 的语言集成也与此类似。


       简单总结一下,MongoDB 的聚合分组计算的操作与存储文档的结构息息相关,丰富的文档结构一方面有利于存储,同时数据查询展示也可以做到多样化,但另一方面也带来了 shell 脚本操作的复杂性,写起来比较不容易, 需要考虑的细节、步骤也比较多。通过上面这几个简单案例的分析比较,可以看到集算器 SPL 在实现分组统计方面能简化操作,降低难度,从而有效地帮助我们解决问题。


原文地址:https://blog.51cto.com/12749034/2355061

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读552次。com.mongodb.MongoQueryException: Query failed with error code 292 and error message 'Executor error during find command :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting.' on server 11.51.141.63:27017 _mongodb 大文件 下载失败
文章浏览阅读635次,点赞9次,收藏8次。MongoDB 是一种 NoSQL 数据库,它将每个数据存储为一个文档,这里的文档类似于 JSON/BSON 对象,具体数据结构由键值(key/value)对组成。
文章浏览阅读2.1k次。和。_mongodb 日期类型
文章浏览阅读1.7k次。Scalestack等客户期待使用MongoDB Atlas Vector Search和Amazon Bedrock构建下一代应用程序
文章浏览阅读970次。SpringBoot整合中间件mongodb、ES_springboot3 elasticsearch json数据
文章浏览阅读673次。MongoDB 简介_尚医通sql
文章浏览阅读1k次,点赞8次,收藏9次。官网下载MongoDB安装包后进行解压(因了解并不深入,故暂不进行详细说明,自行查找其他安装方法,后期了解深入后将进行该教程的完善)在bin目录下使用命令启动:./mongod --config …/mongodb.conf。该文章任然处于完善中,如果存在错误遗漏的地方,欢迎私信联系。安装相关的nuget包后即可通过以下方法连接数据。YX9010_0@的第二十篇文章。
文章浏览阅读1.2k次,点赞17次,收藏26次。社交场景, 使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日
文章浏览阅读686次。您可以使用 update_one() 方法来更新 MongoDB 中调用的记录或文档。update_one() 方法的第一个参数是 query 对象,用于定义要更新的文档。注释:如果查询找到多个记录,则仅更新第一个匹配项。第二个参数是定义文档新值的对象。_python 更新 mongodb 数据
文章浏览阅读1.3k次。首先来学习一下nosql这里安装就不进行介绍 只记录一下让自己了解mongodb。_nosql注入
文章浏览阅读4.1k次,点赞8次,收藏7次。在data的目录下,创建一个db文件。因为启动MongoDB服务之前必须创建数据库文件的存放文件夹,否则命令不会自动创建,而且不能启动成功。第一步:安装时,Custom是指可以自定义安装路径,然后傻瓜式安装即可(注意:先不要安装图形化工具,否则安装时间会特别长):如果要想连接成功,必须要开服务,即mongod -dbpath C:MongoDBdatadb的cmd要一直开着。然后回车,ctrl+F输入port找到端口号,一般为:27017。打开命令行,然后找到bin文件地址,并输入。_mongodb windows安装
文章浏览阅读5.1k次,点赞3次,收藏43次。详细介绍MongoDB数据库的基本知识,安装方法,基本操作,_mongodb数据库
文章浏览阅读3.2k次。安装教程翻看以往文章。_navicat 连接mongodb
文章浏览阅读426次,点赞9次,收藏12次。win10开放端口:https://blog.csdn.net/m0_43605481/article/details/119255256。我的是阿里云服务器,所以直接在安全组中加入规则,端口范围:27017,授权对象:0.0.0.0。windows在mongodb安装文件夹的bin文件夹中的mongod.cfg。数据库名字是test,打算创建一个用户,账号aaa,密码bbb,权限readWrite。因为该用户是创建在test数据库的,所以在最后要加上test。O了,然后恢复了test的数据。
文章浏览阅读1.1k次。聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。MongoDB的聚合查询​聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。MongoDB中聚合(aggregate)主要用于处理数据(例如分组统计平均值、求和、最大值等),并返回计算后的数据结果,有点类似sql语句中的count(*)、groupby。..._如何将几个db的数据统整在一起做查询
文章浏览阅读680次,点赞7次,收藏8次。(2)application.properties配置文件。(4)UserService类。(5)测试和测试结果。
文章浏览阅读1k次,点赞17次,收藏25次。Studio 3T 2023.9 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询_mongodb客户端
文章浏览阅读1.1k次,点赞32次,收藏27次。插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如 MySQL 既可以支持 B-Tree 结构的 InnoDB 存储引擎,还可以支持 LSM 结构的 RocksDB 存储引擎。MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。
文章浏览阅读5.1k次,点赞6次,收藏96次。本文设计了一种基于智能室内温度控制的自动调速风扇。以STM32系列单片机为核心主控板,通过程序代码驱动和使用温度传感器模块实现对环境温度的实时监测,并可以实时显示环境温度。同时,可以设置温度检测的上下警告值,根据需求自行调节。_stm32 温控风扇
文章浏览阅读898次,点赞13次,收藏21次。在MongoDB中,我们使用find()和find_one()方法来在集合中查找数据,就像在MySQL数据库中使用SELECT语句来在表中查找数据一样。_pymongo find_one