MongoDB06-高级查询
- MongoDB全文搜索特性比简单的字符串匹配强大得多,它将基于为文档选择的语言以全词干(full-stemmed)的方式创建出索引,它是基于文档执行语言查询的一个极其强大的工具。
- 全文索引是在MongoDB中创建专有文本索引,通过这些索引可以执行文本搜索,从而定位到包含了匹配文本元素的文档。
- MongoDB聚集框架提供了许多查询函数,可用于遍历所选择的文档或所有文档,并收集或修改信息。这些查询函数将按照管道操作的方式依次在集合中执行,并从查询中收集信息。
- MapReduce是一个强大的机制,可利用MongoDB内置的JavaScript引擎,以实时的方式执行抽象代码。它是一个极其强大的工具,通过使用两个JavaScript函数(第三个是可选的)实现映射操作,一个用于映射数据,另一个用于转换并从映射数据中取出信息。
- 这些是真正的高级特性,如果误用它们,那么可能会在MongoDB节点中引发严重的性能问题。所以,在将这些特性部署到重要系统之前,一定要在测试环境中进行测试。
1、文本索引
- 见:https://www.cnblogs.com/maiblogs/p/16709685.html
2、MongoDB聚合
- MongoDB中的聚合框架是在集合的数据上执行匹配、分组和转换操作的能力。通过创建聚合操作的管道流来实现,这些聚合操作将在数据上依次执行,后面的操作都将基于之前的结果执行。Linux或UNIX shell都将这种行为看成shell的管道操作。
db.collection.aggregate(pipeline, options)
- MongoDB聚合管道中的表达式是无状态的,只能用于计算当前聚合管道的文档。每个表达式的值都会是聚合结果中的文档里的新字段的值。
- 可以在MongoDB聚合管道中使用的表达式有:
- \$sum:计算总和。
- \$avg:计算平均值。
- \$min:获取集合中所有文档对应值得最小值。
- \$max:获取集合中所有文档对应值得最大值。
- \$push:将值加入一个列表中,不会除去重复的值。
- \$addToSet:将值加入一个列表中,会除去重复的值。
- \$first:根据资源文档的排序获取第一个文档数据。
- \$last:根据资源文档的排序获取最后一个文档数据。
- 聚合框架中常用的几个操作:
- \$group:将集合中的文档进行分组,可用于统计结果。
- \$limit:只返回聚合管道中的前n个文档。
- \$skip:跳过聚合管道中的前n个文档,并返回余下的文档。
- \$match:过滤文档,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
- \$sort:对聚合管道中的文档进行排序。
- \$unwind:将文档以某一个列表类型的字段拆分成多个文档,每个拆分出来的文档仅包含该列表的一个元素。
- \$project:修改输入文档的结构。可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档。
- \$out:把聚合管道中的文档输出到集合中,而不是直接返回结果。
- \$lookup:执行类似SQL中join的操作(在技术上是左外连接);也就是说,可以从一个集合中读取文档数据,并将它们与另一个集合中的数据合并起来。
- \$geoNear:输出接近某一地理位置的有序文档。
示例:
//查询media集合中的所有文档 > db.media.find() { "_id" : ObjectId("632139804b894b6430107213"), "number" : 1, "name" : "zhangsan", "age" : 18, "addr" : "shanghai" } { "_id" : ObjectId("632139804b894b6430107214"), "number" : 2, "name" : "lisi", "age" : 19, "addr" : "beijing" } { "_id" : ObjectId("632139804b894b6430107215"), "number" : 3, "name" : "wanger", "age" : 18, "addr" : "beijing" } { "_id" : ObjectId("632139814b894b6430107216"), "number" : 4, "name" : "mazi", "age" : 18, "addr" : "shanghai" } //将media集合中的文档按addr进行分组 > db.media.aggregate({$group: {_id: "$addr"}}) { "_id" : "shanghai" } { "_id" : "beijing" } //将media集合中的文档按addr进行分组,并显示分组中的所有age的值(不去重) > db.media.aggregate({$group: {_id: "$addr", age_push: {$push: "$age"}}}) { "_id" : "shanghai", "age_push" : [ 18, 18 ] } { "_id" : "beijing", "age_push" : [ 19, 18 ] } //将media集合中的文档按addr进行分组,并显示分组中的所有age的值(去重) > db.media.aggregate({$group: {_id: "$addr", age_addToSet: {$addToSet: "$age"}}}) { "_id" : "shanghai", "age_addToSet" : [ 18 ] } { "_id" : "beijing", "age_addToSet" : [ 19, 18 ] } //将media集合中的文档按addr进行分组,并对分组中的age的值进行求和运算 > db.media.aggregate({$group: {_id: "$addr", age_sum: {$sum: "$age"}}}) { "_id" : "shanghai", "age_sum" : 36 } { "_id" : "beijing", "age_sum" : 37 } //将media集合中的文档按addr进行分组,并对分组中的age的值进行求平均值运算 > db.media.aggregate({$group: {_id: "$addr", age_avg: {$avg: "$age"}}}) { "_id" : "shanghai", "age_avg" : 18 } { "_id" : "beijing", "age_avg" : 18.5 } //将media集合中的文档按addr进行分组,并显示分组中的age的最小值 > db.media.aggregate({$group: {_id: "$addr", age_min: {$min: "$age"}}}) { "_id" : "shanghai", "age_min" : 18 } { "_id" : "beijing", "age_min" : 18 } //将media集合中的文档按addr进行分组,并显示分组中的age的最大值 > db.media.aggregate({$group: {_id: "$addr", age_max: {$max: "$age"}}}) { "_id" : "shanghai", "age_max" : 18 } { "_id" : "beijing", "age_max" : 19 } //将media集合中的文档按addr进行分组,并显示分组中第一个的age的值 > db.media.aggregate({$group: {_id: "$addr", age_first: {$first: "$age"}}}) { "_id" : "shanghai", "age_first" : 18 } { "_id" : "beijing", "age_first" : 19 } //将media集合中的文档按addr进行分组,并显示分组中最后一个的age的值 > db.media.aggregate({$group: {_id: "$addr", age_last: {$last: "$age"}}}) { "_id" : "shanghai", "age_last" : 18 } { "_id" : "beijing", "age_last" : 18 }
1、\$group
- \$group可以将集合中的文档按照某个字段的值进行分组,可用于统计结果。在分组时会为每个不同的值创建一个_id(必须是_id)文档,可以使用\$符号引用文档中字段。
//将media集合中的文档按addr进行分组(命令中的_id必须是_id) > db.media.aggregate({$group: {_id: "$addr"}}) { "_id" : "shanghai" } { "_id" : "beijing" } //将media集合中的文档按addr进行分组,并统计每个分组中有多少文档(在结果在添加一个字段) > db.media.aggregate({$group: {_id: "$addr", "addr_count": {$sum: 1}}}) { "_id" : "shanghai", "addr_count" : 2 } { "_id" : "beijing", "addr_count" : 2 } //将media集合中的文档按addr进行分组,并对分组中的age的值进行求和运算 > db.media.aggregate({$group: {_id: "$addr", age_sum: {$sum: "$age"}}}) { "_id" : "shanghai", "age_sum" : 36 } { "_id" : "beijing", "age_sum" : 37 }
2、\$limit
- \$limit可以用来只返回聚合管道中的前n个文档。
//将media集合中的文档按addr进行分组,并统计每个分组中有多少文档(在结果在添加一个字段) > db.media.aggregate({$group: {_id: "$addr", "addr_count": {$sum: 1}}}) { "_id" : "beijing", "addr_count" : 2 } { "_id" : "shanghai", "addr_count" : 2 } //只显示上面结果的前n条 > db.media.aggregate([{$group: {_id: "$addr", "addr_count": {$sum: 1}}}, {$limit: 1}]) { "_id" : "beijing", "addr_count" : 2 }
3、\$match
- \$match可以在聚集管道中返回一个普通MongoDB查询的结果。
- \$match最好用在聚合管道的开始,可以限制输入到管道中的文档的数量。通过限制被处理文档的数量,可以大大地减小性能开销。例如只希望对age小于19的进行统计。
//只对age小于19的进行分组统计 > db.media.aggregate([{$match: {"age": {$lt: 19}}}, {$group: {_id: "$addr", "addr_count": {$sum: 1}}}]) { "_id" : "shanghai", "addr_count" : 2 } { "_id" : "beijing", "addr_count" : 1 } //对上面的结果再次进行$match操作 > db.media.aggregate([{$match: {"age": {$lt: 19}}}, {$group: {_id: "$addr", "addr_count": {$sum: 1}}}, {$match: {"addr_count": {$gt: 1}}}]) { "_id" : "shanghai", "addr_count" : 2 }
4、\$sort
- \$sort可以对聚合管道中的文档进行排序。
//对输出的文档以addt_count字段进行升序排序 > db.media.aggregate([{$match: {"age": {$lt: 19}}}, {$group: {_id: "$addr", "addr_count": {$sum: 1}}}, {$sort: {"addr_count": 1}}]) { "_id" : "beijing", "addr_count" : 1 } { "_id" : "shanghai", "addr_count" : 2 }
5、\$unwind
- \$unwind可以将文档以某一个列表类型的字段拆分成多个新文档,每个新文档(仅在内存中)仅包含该列表的一个元素。
- 注意,操作巨大的集合时,可能会出现问题,占用大量的内存。
//将media集合中的文档按addr进行分组,并显示分组中的所有age的值(不去重) > db.media.aggregate([{$group: {_id: "$addr", age_push: {$push: "$age"}}}]) { "_id" : "shanghai", "age_push" : [ 18, 18 ] } { "_id" : "beijing", "age_push" : [ 19, 18 ] } //将上面结果中的文档以age_push进行拆分 > db.media.aggregate([{$group: {_id: "$addr", age_push: {$push: "$age"}}}, {$unwind: "$age_push"}]) { "_id" : "shanghai", "age_push" : 18 } { "_id" : "shanghai", "age_push" : 18 } { "_id" : "beijing", "age_push" : 19 } { "_id" : "beijing", "age_push" : 18 }
6、\$project
- \$project可以修改输入文档的结构。可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档。
//将media集合中的文档按addr进行分组,并显示分组中的所有age的值(不去重) > db.media.aggregate({$group: {_id: "$addr", age_push: {$push: "$age"}}}) { "_id" : "beijing", "age_push" : [ 19, 18 ] } { "_id" : "shanghai", "age_push" : [ 18, 18 ] } //将上面结果中的文档,不输出_id,输出age_push > db.media.aggregate([{$group: {_id: "$addr", age_push: {$push: "$age"}}}, {$project: {"_id": 0, "age_push": 1}}]) { "age_push" : [ 19, 18 ] } { "age_push" : [ 18, 18 ] } //将上面结果中的文档中的age_push字段重名为age_push_2 > db.media.aggregate([{$group: {_id: "$addr", age_push: {$push: "$age"}}}, {$project: {"_id": 0, "age_push_2": "$age_push"}}]) { "age_push_2" : [ 18, 18 ] } { "age_push_2" : [ 19, 18 ] }
7、\$skip
- \$skip可以跳过聚合管道中的前n个文档,并返回余下的文档。
//将media集合中的文档按addr进行分组,并统计每个分组中有多少文档(在结果在添加一个字段) > db.media.aggregate({$group: {_id: "$addr", "addr_count": {$sum: 1}}}) { "_id" : "beijing", "addr_count" : 2 } { "_id" : "shanghai", "addr_count" : 2 } //跳过上面结果的前n条,并显示余下的 > db.media.aggregate([{$group: {_id: "$addr", "addr_count": {$sum: 1}}}, {$skip: 1}]) { "_id" : "shanghai", "addr_count" : 2 }
8、\$out
- \$out可以把聚合管道中的文档输出到集合中,而不是直接返回结果。(MongoDB 2.6引入)
//将聚合管道的结果输出到addr集合中 > db.media.aggregate([{$group: {_id: "$addr", "addr_count": {$sum: 1}}}, {$out: "addr"}]) //查询addr集合中的所有文档 > db.addr.find() { "_id" : "beijing", "addr_count" : 2 } { "_id" : "shanghai", "addr_count" : 2 } //可以通过$project引入自己的_id字段 > db.addr.find() { "_id" : ObjectId("632188c91d1843efd0f9f042"), "addr_count" : 2 } { "_id" : ObjectId("632188c91d1843efd0f9f043"), "addr_count" : 2 }
9、\$lookup
- \$lookup:执行类似SQL中join的操作(在技术上是左外连接);也就是说,可以从一个集合中读取文档数据,并将它们与另一个集合中的数据合并起来。(MongoDB 3.2引入)
- \$lookup的参数:
- from:右集合。
- localField:使用左集合中的哪个字段与连接右集合。
- foreignField:使用右集合中的哪个字段与连接左集合。
- as:将右集合添加到左集合中的哪个新字段。
//插入数据 db.prima.insert({"number": 1, "english": "one"}) db.prima.insert({"number": 2, "english": "two"}) db.secunda.insert({"number": 1, "ascii": 49}) db.secunda.insert({"number": 2, "ascii": 50}) //合并两个集合的数据,将secunda合并到prima > db.prima.aggregate({$lookup: {from: "secunda", localField: "number", foreignField: "number", as: "secundaDoc"}}) { "_id" : ObjectId("63218ae30aad732da6f9636b"), "number" : 1, "english" : "one", "secundaDoc" : [ { "_id" : ObjectId("63218ae30aad732da6f9636d"), "number" : 1, "ascii" : 49 } ] } { "_id" : ObjectId("63218ae30aad732da6f9636c"), "number" : 2, "english" : "two", "secundaDoc" : [ { "_id" : ObjectId("63218ae40aad732da6f9636e"), "number" : 2, "ascii" : 50 } ] } //使用\$unwind和\$project整理上面的结果 > db.prima.aggregate([{$lookup: {from: "secunda", localField: "number", foreignField: "number", as: "secundaDoc"}}, {$unwind: "$secundaDoc"}, {$project: {"_id": "$number", english: 1, ascii: "$secundaDoc.ascii"}}, {$unwind: "$ascii"}]) { "english" : "one", "_id" : 1, "ascii" : 49 } { "english" : "two", "_id" : 2, "ascii" : 50 }
3、MapReduce
- 聚合管道是Map-Reduce的替代方案。
- 从MongoDB 5.0开始,Map-Reduce已弃用:
- 应该使用聚合管道而不是map-reduce。聚合管道提供了比map-reduce更好的性能和可用性。
- 可以使用聚合管道阶段(如$group、$merge等)重写映射减少操作。
- 对于需要定制功能的map-reduce操作,可以使用$accumulator和$function聚合操作符,从4.4版开始提供。可以使用这些操作符在JavaScript中定义自定义聚合表达式。
- MapReduce是MongoDB中最复杂的查询机制之一。它通过两个JavaScript函数实现查询:map和reduce。这两个函数完全由用户自定义,这就提供了极其强大的灵活性。
- MapReduce将对指定的集合执行一个专门的查询,所有匹配该查询的文档都将被输入到map函数中。
- map函数被设计用于生成键/值对。
- 任何含有多个值的键都将输入到reduce函数中,reduce函数将返回输入数据的聚集结果。
- 最后可以finalize参数(可选的)对数据的显示进行完善。
db.collection.mapReduce( <map>, <reduce>, { out: <collection>, query: <document>, sort: <document>, limit: <number>, finalize: <function>, scope: <document>, jsMode: <boolean>, verbose: <boolean>, bypassDocumentValidation: <boolean> } )
- mapReduce函数的参数释义:
- map:一个JavaScript函数将值与键关联或“映射”并发出键值对的。可以将函数指定为BSON类型JavaScript(即BSON类型13)或String(即BSON类型2)。
- reduce:一个JavaScript函数将与特定键相关的所有值“减少”为一个对象。可以将函数指定为BSON类型JavaScript(即BSON类型13)或String(即BSON类型2)。
- out:可以将结果输出到集合、输出到具有操作的集合或内联输出。当对集合的主要成员执行mapreduce操作时,可以输出到集合,在辅助成员上只能使用内联输出。
- query:使用查询运算符指定选择条件,以确定映射函数的文档输入。
- sort:对输入文档进行排序。此选项对于优化非常有用。例如,指定sort键与emit键相同,以减少reduce操作。排序键必须在此集合的现有索引中。
- limit:为映射函数的输入指定文档的最大数量。
- finalize:可选的。在reduce函数之后修改输出的JavaScript函数。可以将函数指定为BSON类型JavaScript(即BSON类型13)或String(即BSON类型2)。
- scope:指定可在map、reduce和finalize函数中访问的全局变量。
- jsMode:指定是否在执行map和reduce函数之间将中间数据转换为BSON格式。
- verbose:指定是否在结果信息中包含计时信息。将verbose设置为true以包含定时信息。
- collation:可选的。指定要用于操作的排序规则。
- bypassDocumentValidation:可选的。使mapReduce在操作过程中绕过文档验证。这允许您插入不满足验证要求的文档。
1
# #