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的管道操作。
1 | 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:输出接近某一地理位置的有序文档。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | //查询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)文档,可以使用$符号引用文档中字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 | //将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个文档。
1 2 3 4 5 6 7 8 | //将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的进行统计。
1 2 3 4 5 6 7 | //只对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可以对聚合管道中的文档进行排序。
1 2 3 4 | //对输出的文档以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可以将文档以某一个列表类型的字段拆分成多个新文档,每个新文档(仅在内存中)仅包含该列表的一个元素。
- 注意,操作巨大的集合时,可能会出现问题,占用大量的内存。
1 2 3 4 5 6 7 8 9 10 11 | //将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可以修改输入文档的结构。可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 | //将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个文档,并返回余下的文档。
1 2 3 4 5 6 7 8 | //将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引入)
1 2 3 4 5 6 7 8 9 10 11 12 | //将聚合管道的结果输出到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:将右集合添加到左集合中的哪个新字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //插入数据 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更好的性能和可用性。
- 可以使用聚合管道阶段(如merge等)重写映射减少操作。
- 对于需要定制功能的map-reduce操作,可以使用function聚合操作符,从4.4版开始提供。可以使用这些操作符在JavaScript中定义自定义聚合表达式。
- MapReduce是MongoDB中最复杂的查询机制之一。它通过两个JavaScript函数实现查询:map和reduce。这两个函数完全由用户自定义,这就提供了极其强大的灵活性。
- MapReduce将对指定的集合执行一个专门的查询,所有匹配该查询的文档都将被输入到map函数中。
- map函数被设计用于生成键/值对。
- 任何含有多个值的键都将输入到reduce函数中,reduce函数将返回输入数据的聚集结果。
- 最后可以finalize参数(可选的)对数据的显示进行完善。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
1 | # # |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器