MongoDB高级教程

 // https://www.runoob.com/mongodb/mongodb-aggregate.html
// MongoDB 关系   嵌入式关系 or 引用式关系
/*
文档间可以通过嵌入和引用来建立联系
  MongoDB 中的关系可以是:
    1:1 (1对1)
    1: N (1对多)
    N: 1 (多对1)
    N: N (多对多)

 example:一个用户可以有多个地址,一对多的关系   
 user 文档的简单结构:
 {
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "name": "Tom Hanks",
   "contact": "987654321",
   "dob": "01-01-1991"
 }
 address 文档的简单结构:
 {
   "_id":ObjectId("52ffc4a5d85242602e000000"),
   "building": "22 A, Indiana Apt",
   "pincode": 123456,
   "city": "Los Angeles",
   "state": "California"
 } 

  嵌入式关系  比较容易的获取和维护数据, 缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能
{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address": [
      {
         "building": "22 A, Indiana Apt",
         "pincode": 123456,
         "city": "Los Angeles",
         "state": "California"
      },
      {
         "building": "170 A, Acropolis Apt",
         "pincode": 456789,
         "city": "Chicago",
         "state": "Illinois"
      }]
} 

db.users.findOne({"name":"Tom Benzamin"},{"address":1})

引用式关系  经常用到的方法,把用户数据文档和用户地址数据文档分开,通过引用文档的 id 字段来建立关系
{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address_ids": [
      ObjectId("52ffc4a5d85242602e000000"),
      ObjectId("52ffc4a5d85242602e000001")
   ]
}

用户文档的 address_ids 字段包含用户地址的对象id(ObjectId)数组。
这种方法需要两次查询,
    第一次查询用户地址的对象id(ObjectId),
    第二次通过查询的id获取用户的详细地址信息。
var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1})
// 这一句中的 findOne 不能写成 find,因为 find 返回的数据类型是数组,findOne 返回的数据类型是对象。
var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})

// 如果这一句使用了 find,那么下面一句应该改写为:
var addresses = db.address.find({"_id":{"$in":result[0]["address_ids"]}})
 */

// MongoDB 数据库引用  DBRefs vs 手动引用
/*

使用 DBRefs
{ $ref : , $id : , $db :  }

三个字段表示的意义为:
$ref:集合名称
$id:引用的id
$db:数据库名称,可选参数

{
   "_id":ObjectId("53402597d852426020000002"),
   "address": {
   "$ref": "address_home",
   "$id": ObjectId("534009e4d852427820000002"),
   "$db": "runoob"},
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin"
}

通过指定 $ref 参数(address_home 集合)来查找集合中指定id的用户地址信息:
var user = db.users.findOne({"name":"Tom Benzamin"})
var dbRef = user.address
db[dbRef.$ref].findOne({"_id":(dbRef.$id)})
or 
db[dbRef.$ref].findOne({"_id":ObjectId(dbRef.$id)})

返回了 address_home 集合中的地址数据:
{
   "_id" : ObjectId("534009e4d852427820000002"),
   "building" : "22 A, Indiana Apt",
   "pincode" : 123456,
   "city" : "Los Angeles",
   "state" : "California"
}

 */

// MongoDB 覆盖索引查询
/*
所有的查询字段是索引的一部分
所有的查询返回字段在同一个索引中

 users 集合:
{
    "_id" : ObjectId("53402597d852426020000002"),
    "contact" : "987654321",
    "dob" : "01-01-1991",
    "gender" : "M",
    "name" : "Tom Benzamin",
    "user_name" : "tombenzamin"
}
 users 集合中创建联合索引,字段为 gender 和 user_name :
 db.users.ensureIndex({gender:1,user_name:1})
 查询 
 
 MongoDB的不会去数据库文件中查找。相反,它会从索引中提取数据,这是非常快速的数据查询
 db.users.find({gender:"M"},{user_name:1,_id:0}) // _id 字段 不显示
 db.users.find({gender:"M"},{user_name:1}) // _id 字段 默认显示
*/

// MongoDB 查询分析 explain() 和 hint()。
/*
explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化

db.users.ensureIndex({gender:1,user_name:1})
db.users.find({gender:"M"},{user_name:1,_id:0}).explain()

返回如下结果
 {
   "cursor" : "BtreeCursor gender_1_user_name_1", //这个查询使用了索引 也使用了 BtreeCursor 类型的游标  没有使用则显示 BasicCursor
   "isMultiKey" : false,
   "n" : 1, // 当前查询返回的文档数量
   "nscannedObjects" : 0,
   "nscanned" : 1, // 表明当前这次查询一共扫描了集合中多少个文档 这个数值和返回文档的数量越接近越好
   "nscannedObjectsAllPlans" : 0,
   "nscannedAllPlans" : 1,
   "scanAndOrder" : false,
   "indexOnly" : true,  //字段为 true ,表示我们使用了索引。
   "nYields" : 0,
   "nChunkSkips" : 0,
   "millis" : 0, // 当前查询所需时间,毫秒数。
   "indexBounds" : { // 前查询具体使用的索引
      "gender" : [
         [
            "M",
            "M"
         ]
      ],
      "user_name" : [
         [
            {
               "$minElement" : 1
            },
            {
               "$maxElement" : 1
            }
         ]
      ]
   }
}


 使用 hint()
某些情形下会提升性能。 一个有索引的 collection 并且执行一个多字段的查询(一些字段已经索引了)
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()

*/

// MongoDB 原子操作
/*
不要要求mongodb保证数据的完整性

mongodb提供了许多原子操作,比如文档的保存,修改,删除等,都是原子操作
要么这个文档保存到Mongodb,要么没有保存到Mongodb

book = {
          _id: 123456789,
          title: "MongoDB: The Definitive Guide",
          author: [ "Kristina Chodorow", "Mike Dirolf" ],
          published_date: ISODate("2010-09-24"),
          pages: 216,
          language: "English",
          publisher_id: "oreilly",
          available: 3,
          checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]
        }

使用 db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。

db.books.findAndModify ( {
   query: {
            _id: 123456789,
            available: { $gt: 0 }
          },
   update: {
             $inc: { available: -1 },
             $push: { checkout: { by: "abc", date: new Date() } }
           }
} )

原子操作常用命令
$set  用来指定一个键并更新键值,若键不存在并创建。
{ $set : { field : value } }

$unset 用来删除一个键。
{ $unset : { field : 1} }

$inc  对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作
{ $inc : { field : value } }

$push  把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。
{ $push : { field : value } }

$pushAll 一次可以追加多个值到一个数组字段内
{ $pushAll : { field : value_array } }

$pull 从数组field内删除一个等于value值。
{ $pull : { field : _value } }

$addToSet 增加一个值到数组内,而且只有当这个值不在数组内才增加。

$pop 删除数组的第一个或最后一个元素
{ $pop : { field : 1 } }

$rename 修改字段名称
{ $rename : { old_field_name : new_field_name } }

$bit 位操作,integer类型
{$bit : { field : {and : 5}}}

偏移操作符

t.find() { 
"_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), 
"title" : "ABC", 
"comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] 
}

t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )

t.find() { 
"_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", 
"comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] 
}
 */

// MongoDB 高级索引
/*
users 集合
{
   "address": {
      "city": "Los Angeles",
      "state": "California",
      "pincode": "123"
   },
   "tags": [
      "music",
      "cricket",
      "blogs"
   ],
   "name": "Tom Benzamin"
}

以上文档包含了 address 子文档和 tags 数组。

索引数组字段
在数组中创建索引,需要对数组中的每个字段依次建立索引
对集合中的数组 tags 建立索引
为数组 tags 创建索引时,会为 music、cricket、blogs三个值建立单独的索引。

db.users.ensureIndex({"tags":1}) // 创建数组索引:
db.users.find({tags:"cricket"})  // 检索集合的 tags 字段
db.users.find({tags:"cricket"}).explain() //使用 explain 命令
显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引

索引子文档字段
  通过city、state、pincode字段来检索文档,由于这些字段是子文档的字段
  对子文档建立索引

  db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})   //为子文档的三个字段创建索引
  db.users.find({"address.city":"Los Angeles"}) // 使用子文档的字段来检索数据
  db.users.find({"address.state":"California","address.city":"Los Angeles"})  // 查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化
  db.users.find({"address.city":"Los Angeles","address.state":"California","address.pincode":"123"})
 */

// MongoDB 索引限制
/*
额外开销       在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
内存(RAM)使用  索引是存储在内存(RAM)中,索引的大小不超过内存的限制
查询限制
    索引不能被以下的查询使用:
 
    正则表达式及非操作符,如 $nin, $not, 等。
    算术运算符,如 $mod, 等。
    $where 子句
检测你的语句是否使用索引是一个好的习惯,可以用explain来查看

索引字段的值超过索引键的限制,MongoDB中不会创建索引

最大范围
    集合中索引不能超过64个
    索引名的长度不能超过128个字符
    一个复合索引最多可以有31个字段
 */

// MongoDB ObjectId
/*
ObjectId 是一个12字节 BSON 类型数据,有以下格式:
前4个字节表示时间戳
接下来的3个字节是机器标识码
紧接的两个字节由进程id组成(PID)
最后三个字节是随机数。

文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。
采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)?
答 在多个 服务器上同步自动增加主键值既费力还费时。

创建新的ObjectId
  newObjectId = ObjectId()
  myObjectId = ObjectId("5349b4ddd2781d08c09890f4")

创建文档的时间戳
  ObjectId("5f221b03722698a5ff7493ab").getTimestamp()

ObjectId 转换为字符串
new ObjectId().str

 */

// MongoDB Map Reduce
/*
Map-Reduce是一种计算模型,就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

MapReduce 命令
db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

Map 函数和 Reduce 函数
Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 
将 key 与 value 传递给 Reduce 函数进行处理。

参数说明:
    map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
    reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。。
    out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
    query 一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
    sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
    limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

db.posts.insert([
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
},
{
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"active"
}

])

在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status:"active"),并通过user_name分组,计算每个用户的文章数
db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
)
mapReduce 输出结果为:
{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}
具体参数说明:

result:储存结果的collection的名字,这是个临时集合,MapReduce的连接关闭后自动就被删除了。
timeMillis:执行花费的时间,毫秒为单位
input:满足条件被发送到map函数的文档个数
emit:在map函数中emit被调用的次数,也就是所有集合中的数据总量
ouput:结果集合中的文档个数(count对调试非常有帮助)
ok:是否成功,成功为1
err:如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大

使用 find 操作符来查看 mapReduce 的查询结果:
db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
).find()
/* 1 */
{
    "_id" : "mark",
    "value" : 4.0
}
/* 2 */
{
    "_id" : "runoob",
    "value" : 1.0
}

临时集合参数是这样写的
out: { inline: 1 }
设置了 {inline:1} 将不会创建集合,整个 Map/Reduce 的操作将会在内存中进行
这个选项只有在结果集单个文档大小在16MB限制范围内时才有效
 db.users.mapReduce(map,reduce,{out:{inline:1}});
 */

// MongoDB 全文检索
/*
对每一个词建立一个索引,指明该词在文章中出现的次数和位置,
当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
类似于通过字典中的检索字表查字的过程

支持全文检索,目前支持15种语言的全文索引
    danish           german            romanian
    dutch            hungarian         russian
    english          italian           spanish
    finnish          norwegian         swedish
    french           portuguese        turkish

启用全文检索
    db.adminCommand({setParameter:true,textSearchEnabled:true})
    或者使用命令:
    mongod --setParameter textSearchEnabled=true    

创建全文索引
    posts 集合的文档数据,包含了文章内容(post_text)及标签(tags)
    {
       "post_text": "enjoy the mongodb articles on Runoob",
       "tags": [
          "mongodb",
          "runoob"
       ]
    }

    db.posts.ensureIndex({post_text:"text"})
    db.posts.find({$text:{$search:"runoob"}})
    { 
       "_id" : ObjectId("53493d14d852429c10000002"), 
       "post_text" : "enjoy the mongodb articles on Runoob", 
       "tags" : [ "mongodb", "runoob" ]
    }
    或
    db.posts.runCommand("text",{search:"runoob"})

    db.posts.getIndexes()                   //查询索引
    db.posts.dropIndex("post_text_text")    //删除索引

 */

// MongoDB 正则表达式
/*
是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串

以下 posts 集合的文档结构,该文档包含了文章内容和标签
{
   "post_text": "enjoy the mongodb articles on runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}

使用正则表达式查找包含 runoob 字符串的文章:
db.posts.find({post_text:{$regex:"runoob"}})
或
db.posts.find({post_text:/runoob/})

不区分大小写的正则表达式
$options 为 $i
db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})

数组元素使用正则表达式
要查找包含以 run 开头的标签数据(ru 或 run 或 runoob), 你可以使用以下代码:
db.posts.find({tags:{$regex:"run"}})

优化正则表达式查询
    使用索引相比于正则表达式匹配查找所有的数据查询速度更快
    如果正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始。
    例如: 如果正则表达式为 ^tut ,查询语句将查找以 tut 为开头的字符串。

正则表达式中使用变量。
    一定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。
    否则没有报错信息,只是结果为空!实例如下
    var name=eval("/" + 变量值key +"/i"); 

    模糊查询包含title关键词, 且不区分大小写:
    title:eval("/"+title+"/i")    // 等同于 title:{$regex:title,$Option:"$i"}   

regex操作符的介绍

regex操作符
    {<field>:{$regex:/pattern/,$options:’<options>’}}
    {<field>:{$regex:’pattern’,$options:’<options>’}}
    {<field>:{$regex:/pattern/<options>}}

正则表达式对象
    {<field>: /pattern/<options>}

$regex与正则表达式对象的区别:
    在$in操作符中只能使用正则表达式对象,例如:{name:{$in:[/^joe/i,/^jack/}}
    在使用隐式的$and操作符中,只能使用$regex,例如:{name:{$regex:/^jo/i, $nin:['john']}}
    当option选项中包含X或S选项时,只能使用$regex,例如:{name:{$regex:/m.*line/,$options:"si"}}    

$regex操作符的使用
    option选项可以改变正则匹配的默认行为,它包括i, m, x以及S四个选项,其含义如下
    i 忽略大小写, {<field>{$regex/pattern/i}}
    m 多行匹配模式 {<field>{$regex/pattern/,$options:'m'}  
       m选项会更改^和$元字符的默认行为
      分别使用与行的开头和结尾匹配,而不是与输入字符串的开头和结尾匹配。
    x 忽略非转义的空白字符 {<field>:{$regex:/pattern/,$options:'x'}
      设置x选项后,正则表达式中的非转义的空白字符将被忽略,
      同时井号(#)被解释为注释的开头注,只能显式位于option选项中
    s 单行匹配模式{<field>:{$regex:/pattern/,$options:'s'}
      设置s选项后,会改变模式中的点号(.)元字符的默认行为
      它会匹配所有字符,包括换行符(\n),只能显式位于option选项中。   

    i,m,x,s可以组合使用,例如:{name:{$regex:/j*k/,$options:"si"}}
    在设置索弓}的字段上进行正则匹配可以提高查询速度,
    而且当正则表达式使用的是前缀表达式时,查询速度会进一步提高,例如:{name:{$regex: /^joe/}
 */

// Rockmongo 下载地址:https://github.com/iwind/rockmongo
// Navicat for MongoDB 下载地址:https://www.navicat.com.cn/products

// MongoDB 固定集合(Capped Collections)
/*
有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,
当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!

通过createCollection来创建一个固定集合,且capped选项设置为true:
   db.createCollection("cappedLogCollection",{capped:true,size:10000})
   指定文档个数,加上max:1000属性
   db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000}) 

判断集合是否为固定集合:
   db.cappedLogCollection.isCapped()

将已存在的集合转换为固定集合可以使用以下命令:已存在的 posts 集合转换为固定集合。
    db.runCommand({"convertToCapped":"posts",size:10000})

固定集合查询
    固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,
    也可以使用$natural调整返回顺序。
    db.cappedLogCollection.find().sort({$natural:-1})

固定集合的功能特点
    可以插入及更新,但更新不能超出collection的大小,否则更新失败,
    不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。

    在32位机子上一个cappped collection的最大值约为482.5M,
    64位上只受系统文件大小的限制。

固定集合属性及用法
    属性
        属性1:对固定集合进行插入速度极快
        属性2:按照插入顺序的查询输出速度极快
        属性3:能够在插入最新数据时,淘汰最早的数据
    用法
        用法1:储存日志信息
        用法2:缓存一些少量的文档

db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
    size 是整个集合空间大小,单位为【KB】
    max 是集合文档个数上线,单位是【个】    
    如果空间大小到达上限,则插入下一个文档时,会覆盖第一个文档;
    如果文档个数到达上限,同样插入下一个文档时,会覆盖第一个文档。两个参数上限判断取的是【与】的逻辑。
 */

// MongoDB 自动增长
/*
使用 counters 集合
    以下 products 文档。我们希望 _id 字段实现 从 1,2,3,4 到 n 的自动增长功能。
    {
      "_id":1,
      "product_name": "Apple iPhone",
      "category": "mobiles"
    }
    
    创建 counters 集合,序列字段值可以实现自动长:
    db.createCollection("counters")
    向 counters 集合中插入以下文档,使用 productid 作为 key:
    {
      "_id":"productid",
      "sequence_value": 0
    }
    插入 counters 集合的序列文档中:
    db.counters.insert({_id:"productid",sequence_value:0})

    创建 Javascript 函数
    function getNextSequenceValue(sequenceName){
       var sequenceDocument = db.counters.findAndModify(
          {
             query:{_id: sequenceName },
             update: {$inc:{sequence_value:1}},
             "new":true
          });
       return sequenceDocument.sequence_value;
    }
    
    getNextSequenceValue("productid")
    
    db.products.insert({
       "_id":getNextSequenceValue("productid"),
       "product_name":"Apple iPhone",
       "category":"mobiles"
    })

    db.products.find()
    db.products.count()
 */

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页