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

#                                                                                                                               #
posted @ 2022-09-13 16:47  麦恒  阅读(64)  评论(0编辑  收藏  举报