MongoDB——聚合框架

使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理。这些构件包括筛选(filtering)、投射(projecting)、 分组(grouping)、 排序(sorting)、 限制(limiting)和跳过(skipping)。

例如,有一个保存着杂志文章的集合,你可能希望找出发表文章最多的那个作者。假设每篇文章被保存为MongoDB中的一个文档,可以按照如下步骤创建管道。

  1. 将每个文章文档中的作者投射出来。
  2. 将作者按照名字排序,统计每个名字出现的次数。
  3. 将作者按照名字出现次数降序排列。
  4. 将返回结果限制为前5个。

这里面的每一步都对应聚合框架中的一个操作符:

  1. {"$project": {"autho":1}}
    这样可以将author从每个文档中投射出来。
    这个语法与查询中的字段选择器比较像:可以通过这顶"fieldname":1选择需要投射的字段,或者通过指定"fieldname":0排除不需要的字段。
    执行完这个"$project"操作之后,结果集中的每个文档都会以{"_id":id, “autho”:“authorName”}这样的形式表示。这些结果只会在内存中存在,不会被写入磁盘。
  2. {"$group":{"_id": "$author", "count":{"$sum":1}}}
    这样就会将作者按照名字排序,某个作者的名字每出现一次,就会对这个作者的’ count"加1。
    这里首先指定了需要进行分组的字段"author"。这是由" id" : “$author"指定的。可以将这个操作想象为:这个操作执行完后,每个作者只对应一个结果文档,所以"author"就成了文档的唯一标识符(” id")。
    第二个字段的意思是为分组内每个文档的"count"字段加1。注意,新加入的文档中并不会有"count" 字段;这"$group"创建的一个新字段。
    执行完这一步之后,结果集中的每个文档会是这样的结构: {"_ id" : “authorName” , “count” : articleCount}。
  3. {"$sort":{"count":-1}}
    这个操作会对结果集中的文档根据count字段进行降序排列
  4. {"$limit": 5}
    这个操作将最终的返回结果限制为当前结果的前5个文档。

在这里插入图片描述
在这里插入图片描述
如果管道没有给出预期的结果,就需要进行调试,调试时,可以先只指定;第一个管道操作符。如果这时得到了预期结果,那就再指定第二个管道操作符。以前面的例子来说,首先要试着只使用"$project"操作符进行聚合;如果这个操作符的结果是有效的,就再添加"$group"操作符;如果结果还是有效的,就再添加"$sort";最后再添加"$limit"操作符。这样就可以逐步定位到造成问题的操作符。

管道操作符

每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符(对于最后一个管道操作符,是将结果返回给客户端)。

不同的管道操作符可以按任意顺序组合在一起使用,而且可以被重复任意多次使用。

$match

$match用于对文档集合进行筛选,之后就可以在筛选得到的文档子集上做聚合。例如,如果想对Oregon (俄勒冈州,简写为OR)的用户做统计,就可以使用{$match : {“state” : “OR”}}。 " $match"可以使用所有常规的查询操作符("$gt"、"$lt"、 “$in"等)。有一个例外需要注意:不能在” $match"中使用地理空间操作符。
在这里插入图片描述

$project

使用$project可以从子文档中提取字段,可以重命名字段。
默认情况下,如果文档中存在_id字段,这个字段就会被返回(_id字段可以被一些管道操作符移除,也可能已经被之前的投射操作给移除了)。
在这里插入图片描述

也可以将投射过的字段进行重命名。例如将_id字段重命名为userId
在这里插入图片描述

注意,必须明确指定将"_id"排除,否则这个字段的值会被返回两次:一次被标为"userId",一次被标为 " _id"。可以使用这种技术生成字段的多个副本,以便在之后的"$group" 中使用。

在对字段进行重命名时,MongoDB并不会记录字段的历史名称。因此,如果原字段名上有一个索引,聚合框架无法在后续的排序操作中使用这个索引。

== 1. 管道表达式:==

最简单的"$project"表达式是包含和排除字段,以及字段名称("$fieldname")。但是,还有一些更强大的选项。也可以使用表达式(expression) 将多个字面量和变量组合在一个值中使用。

2. 数学表达式:
算术表达式可用于操作数值。指定一组数值,就可以使用这个表达式进行操作了。

  • $add[expr1[, expr2, ..., exprN]]
    该操作符接受一个或多个表达式作为参数,将这些表达式相加

  • $subtract[expr1, expr2]
    接受两个表达式作为参数,用第一个表达式减去第二个表达式作为结果

  • $multiply[expr1[, expr2, ..., exprN]]
    接受一个或者多个表达式,并且将它们相乘

  • $mod[expr1, expr2]
    接受两个表达式,将第一个表达式除以第二个表达式得到的余数作为结果

在这里插入图片描述
复杂嵌套:((_id + 2) * 5 - 10) / 2
在这里插入图片描述

3. 日期表达式:
用于提取日期信息,只能对日期类型的字段进行日期操作,不能对数值类型字段做日期操作。

  • $yaer
  • $month
  • $week
  • $dayOfMonth
  • $dayOfWeek
  • $dayOfYear
  • $hour
  • $minute
  • $second
    在这里插入图片描述

4. 字符串表达式:

  • substr[expr, startOffset, numToReturn]
    其中第一个参数expr必须是个字符串,这个操作会截取这个字符串的子串(从startOffset字节开始的numToReturn字节)。
    在这里插入图片描述
    一个中文3个字节:
    在这里插入图片描述

  • $concat[expr1[, expr2, ..., exprN]]
    将给定的表达式(或字符串)连接在一起作为返回结果。
    在这里插入图片描述

  • $toLower[expr1, [expr2, ..., exprN]]
    参数expr必须是一个字符串值,这个操作返回expr的小写形式。
    在这里插入图片描述

  • $toUpperexpr
    参数expr必须是个字符串值,这个操作返回expr的大写形式:

在这里插入图片描述

5. 逻辑表达式:

  • $cmp[expr1, expr2]
    比较expr1和expr2。如果expr1等于expr2,返回0;如果expr1<expr2,返回一个负数;如果expr1>expr2,返回一个正数。

在这里插入图片描述

  • $strcasecmp[string1, string2]
    比较stirng1和string2,区分大小写。只对罗马字符组成的字符串有效。

在这里插入图片描述

  • $eq、$ne、$gt、$gte、$lt、$lte[expr1, expr2]
    对expr1和expr2执行相应的比较操作,返回比较结果(true或false)

  • $and[expr1[,expr2, ..., exprN]]
    只要有任意表达式的值为true,就返回true,否则返回false

  • $not:expr
    对expr取反

  • $cond[boolanExpr, trueExpr, falseExpr]
    如果booleanExpr的值是true,那就返回trueExpr,否则返回falseExpr,如果是老师最欣赏的学生,那么分数就是100
    在这里插入图片描述

  • $ifNull[expr, replacementExpr]
    如果expr是null,返回replacementExpr,否则返回expr

在这里插入图片描述

6. 示例:
加入有个教授想通过某种比较复杂的计算为学生打分:出勤率占10%,日常测验成绩占30%,期末考试占60%:

db.students.aggregate(
	{
		"$project": {
			"grade": {
				"$cond": [
					"$teachersPet", 100, {
						"$add": [
							{"$multiply":[.1, "$attendanceAvg"]},
							{"multiply":[.3, "quizzAvg"]},
							{"multiply":[.6, "$testAvg"]}
						]
					}
				]
			}
		}
	}
)
$group

$group操作可以将文档依据特定字段的不同值进行分组。如果选定了需要进行分组的字段,就可以将选定的字段传递给$group函数的_id字段。
在这里插入图片描述

1. 分组操作符:

分组操作符允许对每个分组进行极端,得到相应的结果。

2. 算术操作符:

  • $sum:value
    对于分组中的每一个文档,将value与计算结果相加。
    在这里插入图片描述

  • $avg:value
    返回每个分组的平均值
    在这里插入图片描述

3. 极值操作符:

  • $max:expr
    返回分组内最大值

  • $min:expr
    返回分组内的最小值

  • $first:expr
    返回分组的第一个值,忽略后面所有值。只有排序之后,明确知道数据顺序时这个操作才有意义

  • $last:expr
    与$fisrt相反,返回分组的最后一个值

在这里插入图片描述

4. 数组操作符:

  • $addToSet:expr
    如果当前数组中不包含expr,那就将它添加到数组中。在返回结果集中,每个元素最多只出现一次,而且元素的顺序是不确定的。

在这里插入图片描述

  • $push:expr
    不管expr是什么值,都将它添加到数组中。返回包含所有值的数组。

在这里插入图片描述

5. 分组行为:
有两个操作符不能用前面介绍的流式工作方式对文档进行处理,"$group"是其中之一。大部分操作符的工作方式都是流式的,只要有新文档进入,就可以对新文档进行处理,但是"$g roup"必须要等收到所有的文档之后,才能对文档进行分组,然后才能将各个分组发送给管道中的下一个操作符。

这意味着,在分片的情况下,"$group"会先在每个分片上执行,然后各个分片上的分组结果会被发送到 mongos再进行最后的统一分组,剩余的管道工作也都是在 mongos(而不是在分片)上运行的。

$unwind

拆分(unwind)可以将数组中的每一个值拆分为单独的文档。
在这里插入图片描述

如果希望在询中得到特定的子文档,这个操作符就会非常有用:先使用$unwind"得到所有子文档,再使用" match"得到想要的文档。
在这里插入图片描述

$sort

可以根据任何字段(或者多个字段)进行排序,与在普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一阶段进行排序,这时的排序操作可以使用索引。否则,排序过程就会比较慢,而且会占用大量内存

在这里插入图片描述

$limit

$limit会接受一个数字n,返回结果集中的前n个文档。

在这里插入图片描述

$skip

$skip也是接受一个数字n,丢弃结果集中的前n个文档,将剩余文档作为结果返回。在“普通”查询中,如果需要跳过大量的数,那么这个操作符的效率会很低。
在聚合中也是如此,因为它必须要先匹配到所有需要跳过的文档,然后再将这些文档丢弃。

在这里插入图片描述

使用管道

应该尽量在管道的开始阶段(执行$project$group或者$unwind操作之前)就尽可能多的文档和字段过滤掉。管道如果不是直接从原先的结合中使用数据,那就无法在筛选和排序中使用索引。

MongoDB不允许单一的聚合操作占用过多的系统内存:如果 MongoDB发现某个聚合操作占用了20%以上的内存,这个操作就会直接输出错误。

允许将输出结果利用管道放入一个集合中是为了方便以后使用(这样可以将所需的内存减至最小)。如果能够通过"$ match"操作迅速减小结果集的大小,就可以使用管道进行实时聚合。由于管道会不断包含更多的文档,会越来越复杂,所以几乎不可能实时得到管道的操作结果。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
mongodb-orm简介Mongodb ORM是基于java的ORM框架,简化了SDK的使用,使代码变得更清晰、简单。 与Ibatis类似,将查询、执行语句封装在xml中,与代码隔离。简称MQL。 项目中使用加入mongodb orm的支持包1. 添加jar包或maven支持<dependency>     <groupId>com.mongodborm</groupId>     <artifactId>mongodb-orm</artifactId>     <version>0.0.1-RELEASE</version> </dependency>2. 初始化mongodb templet        spring中初始化<bean id="mongoTemplet" class="com.mongodb.client.MongoClientTemplet">     <property name="factory">         <bean class="com.mongodb.client.MongoORMFactoryBean">             <property name="dataSource">                 <bean class="com.mongodb.client.MongoDataSource">                     <property name="nodeList" value="127.0.0.1:27017" />                     <property name="dbName" value="your db name" />                     <property name="userName" value="user name" />                     <property name="passWord" value="password" /> <!-- 可使用默认值 --> <property name="connectionsPerHost" value="" />                     <property name="threadsAllowedToBlock" value="" />                     <property name="connectionTimeOut" value="" />                     <property name="maxRetryTime" value="" />                     <property name="socketTimeOut" value="" />                 </bean>             </property>             <property name="configLocations">                 <list>                     <value>classpath:mql/mongo-mql.xml</value>                 </list>             </property>         </bean>     </property> </bean>        代码初始化    try {       Resource resource =  new ClassPathResource("mongo-mql.xml");           MongoORMFactoryBean factory = new MongoORMFactoryBean();       factory.setConfigLocations(new Resource[]{resource});       factory.init();          MongoClientTemplet templet = new MongoClientTemplet();       templet.setFactory(factory);       templet.init();     } catch(Exception e) {       e.printStackTrace();     }编写MQLMapping<mapping id="model" class="test.mongodborm.Model">         <property column="_id" name="id" />         <property column="name" name="name" />         <property column="time" name="time" value="0" />         <property column="status" name="status" /> </mapping> <mapping id="extendModel" class="test.mongodborm.Model" extends="model">     <property column="newProperty" name="newProperty" /> </mapping>  select<select id="queryModelList" collection="test_sample">     <query class="java.lang.String">         <property column="name" name="${value}" />     </query>     <field mapping="model" />     <order>         <property column="time" value="desc" />     </order> </select> update/findAndModify<update id="updateModel" collection="test_sample">     <query class="test.mongodborm.Model$Child">         <property column="name" name="name" ignoreNull="true" />         <property column="time" operate="gte" value="0" type="number" />         <property column="status" operate="in">             <list type="number">0,1</list>         </property>     </query>     <action class="java.util.Map">         <property column="name" name="name" operate="set" />         <property column="status" operate="set" />     </action> </update>有嵌套的查询<select id="queryModelList3" collection="test_sample">     <query class="java.lang.String">         <property column="_id" value="${value}" />         <property column="time" value="0" type="number" />     </query>     <field class="java.util.Map">         <property column="name" name="name" />         <property column="parent" name="parent">             <value class="test.mongodborm.Model$Parent">                 <property column="name" name="name" />                 <property column="child" name="child">                     <value class="test.mongodborm.Model$Child">                         <property column="name" name="name" />                         <property column="time" name="time" value="0" />                     </value>                 </property>                 <property column="data" name="data">                     <value class="java.util.Map">                         <property column="title" name="title" />                         <property column="content" name="content" />                     </value>                 </property>             </value>         </property>         <property column="data" name="data">             <value class="java.util.Map">                 <property column="title" name="title" />                 <property column="content" name="content" />             </value>         </property>     </field>     <order class="java.util.Map">         <property column="time" name="time" value="desc" />     </order> </select>Templet用法Model model = mongoTemplet.findOne("queryModelList", "yuxiangping"); List<Model> list = mongoTemplet.findOne("queryModelList", ""); Model model = new Model(); model.setTime(1L); Map<String, String> action = new HashMap<String, String>(); action.put("name", "yuxiangping-update"); int update = mongoT emplet.update("updateModel", model, action);        更多的使用方法参见 sample.xml 标签:Mongodb
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值