专栏首页earthchen的专栏MongoDB权威指南学习笔记(1)--基础知识与对文档的增删改查

MongoDB权威指南学习笔记(1)--基础知识与对文档的增删改查

Mongo

基础知识与对文档的增删改查

基础知识

文档

文档就是键值对的一个有序集,例如

{"greeting":"hello"}

文档中的值可以时多种不同的数据类型;文档中的键时字符串,但有少数例外情况

  • 键不能含有\0(空字符)
  • .和$具有特殊含义,只能在特定环境下使用

集合

集合就是一组文档,一个集合就相当于关系数据库的一张表

动态模式

集合时动态模式的,就是说集合里面的文档可以时各式各样的。

命名

命名需要满足以下条件:

  • 不能是空字符串
  • 不能包含\0字符
  • 不能以system.开头
  • 不能包含$
子集合

使用.来分割不同命名空间的子集合,例如一个博客系统可能包含两个集合,分别时blog.posts和blog.authors。

数据库

数据库就是多个集合,一个mongo实例可以承载多个数据库,每个数据库可以有多个集合,每个数据库都有独立的权限。

数据库命名需要满足以下条件

  • 不能是空字符串
  • 不能含有特殊字符,基本只能使用字母和数字
  • 区分大小写,(应全部小写)
  • 最多为64字节

有一些数据库名时保留的,可以直接访问这些特殊含义的数据库

  • admin: root数据库
  • local: 不可复制,所有本地集合都可以存储在其中
  • config: 用于存储分片信息

shell

功能完备的JavaScript解释器,可以运行任意JavaScript程序

mongodb客户端

  1. db: 查看当前指向那个数据库 $ db
  2. use: 选择数据库 $ use foobar

基本操作

创建

insert函数将一个文档添加到集合中。

// 声明post变量
post={
    "title":"my blog test",
    "content":"blog post",
    "date":new Date()
}
// 插入blog集合
db.blog.insert(post)

查询调用find方法

db.blog.find()
读取
  • find():查询所有文档(shell会自动显示最多20个匹配的文档)
  • findOne():查询一个文档
更新

使用update()进行更新操作,接受两个参数,第一个限定条件,第二个时新的文档。

post.comments=[]
db.blog.update({title:"my blog test"},post)
删除

使用remove()方法将文档从数据库永久删除

  • 如果不携带参数,会将集合内所有文档都删除
  • 携带一个限定条件作为参数,会删除指定文档

数据类型

基本数据类型

  • null
  • 布尔型
  • 数值
  • 字符串
  • 日期
  • 正则表达式
  • 数组
  • 内嵌文档
  • 对象id
  • 二进制数据
  • 代码

使用MongoDB shell

在启动shell指定机器名和端口,就可以连接不同的机器

$ mongo some-host:30000/myDB

使用shell执行脚本

  • 在shell中传递脚本 $ mongo script.js
  • 使用laod(),从交互式shell运行脚本 load("scipt.js")

在脚本中可以访问db变量,以及其他全局变量,然而shell辅助函数不可以在文件中使用

  • 在shell中使用run()执行命令行程序 run("ls","-l")
  • 如果某些脚本被频繁加在,可以将他们添加到mongorc.js文件中,这个文件会在启动shell时自动运行

创建 更新 删除文档

插入并保存

使用insert()方法向目标集合插入一个文档

db.foo.insert({"bar":"baz"})

批量插入

使用batchInsert()方法向目标集合批量插入文档

db.foo.insert([{"_id":0},{"_id":1},{"_id":2}])

  • 不能在单词请求中将多个文档批量插入多个集合中
  • 如果在执行批量插入的过程中有一个文档插入失败,那么在该文档之前的所有文档都会成功插入,这个文档之后的文档都会插入失败
  • 插入文档的_id不能重复
  • 在批量插入遇到错误时,可以使用continueOnError选项忽略错误并继续执行后续插入,但在shell中并不支持,在驱动中可以执行

插入校验

mongo只对数据进行最基本的检查,检查文档的基本结构,如果没有_id字段,就自动增加一个,并且所有文档都必须小于16MB

删除文档

使用remove()删除

删除速度

删除文档通常很快,如果要清空集合,建议使用drop直接删除集合(然后在空集合上重建索引)

更新文档

使用update()进行更新

更新操作不可分割,先到先执行

文档替换

用一个新文档完全替换匹配的文档,这适用于大规模迁移的情况

使用修改器

通常文档只会有一部分字段要更新,所以可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。

更新修改器是种特殊的键,用来制定复杂的更新操作

设置操作

$set修改器:用来制定一个字段的值,如果这个字段不存在,则创建它。这对更新模式或者增加用户定义的键非常方便。

  1. 向已有的集合中添加字段
db.blog.update(
    {
    "_id":ObjectId("5ace2559f02a40eb6148fc34")
     },
    {
        "$set":{
                "test":"test"
            }
      }
)
  1. 修改集合中已有的字段(可以修改键的类型或内嵌文档)

增加、修改、删除键时,应该使用$修改器

增加减少操作

$inc修改器:用来增加已有键的值,如果该键不存在那就创建一个。对于更新分析数据、因果关系等有数值变化的地方非常方便

更新id为xxx的value=value+1

db.foo.update({
            "_id":ObjectId("5ace332ff02a40eb6148fc36")
        },
        {
            "$inc":{
                "value":1
            }
    }
)

  • 除了_id不能修改其他都可以修改
  • $set用法类似,专门用来增加或减少数字的
  • 只能用于整型、长整型或双精度浮点型的值
数组修改器

有一大类修改器可以用于操作数组

添加元素

push修改器:如果数组已经存在,push会向已有的数组末尾加入一个元素,要是没有就创建一个新的数组

db.blog.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
     },
    {
        "$push":{
            "posts":{
                "name":"joe",
                    "email":"xxx@qq.com"
            }
        }
    }
)


/* 1 */
{
    "_id" : ObjectId("5ace2559f02a40eb6148fc34"),
    "title" : "my blog test2",
    "content" : "blog post2",
    "date" : ISODate("2018-04-11T15:10:17.952Z"),
    "test" : "test",
    "posts" : [ 
        {
            "name" : "joe",
            "email" : "xxx@qq.com"
        }
    ]
}

以上是一种比较简单的push使用形式,也可以应用在一些比较复杂的数组操作,使用each子操作符,可以通过一次

添加多个元素到数组中

db.blog.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
     },
    {
        "$push":{
            "posts":{
                "$each":[111,1111,111111]
            }
        }
    }
)

如果希望数组的长度时固定的,可以使用slice和push组合在一起使用,可以保证数组不会超过设定好的最大长度,实际上就得到了一个最多包含n个元素的数组

db.blog.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
     },
    {
        "$push":{
            "posts":{
                "$each":[111,1111,111111],
                "$slice":-10
            }
        }
    }
)

  • slice的值必须时负整数,如果数组的元素数量小鱼10(push之后),那么所有元素都会被保留,如果数组的元素大于10,那么只有最后10个元素会被保留。

可以在清理元素之前使用$sort,只要向数组中添加子对象就需要清理

db.blog.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
     },
    {
        "$push":{
            "posts":{
                "$each":[111,1111,111111],
                "$slice":-10,
                "$sort":{
                    "rating":-1
                }
            }
        }
    }
)

这样会根据rating字段的值对数组中所有元素进行排序,然后保留前10个。

不能只将slice或者sort和push配合使用,且必须使用each

将数组作为数据集使用

如果想将数组作为数据集使用,保证数组内的元素不会重复。可以使用$ne实现。

例如:要是作者不在引文列表中,就添加进去

db.papers.update(
    {
        "authors cited":{
            "$ne": "Richie"
        }
    },
    {
        "$push":{
            "authors cited":"Richie"
        }
    }
)

实现上述需求,还可以使用addToSet实现,有些时候,更适合用addToSet例如:有一个表示用户的文档,已经有了电子邮件地址的数据集,添加新地址时,用

db.users.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
    },
    {
        "$addToSet":{
            "emails":"doe@gmail.com"
        }
    }
)

addToSet和each组合可以实现添加多个不同的值,可以一次添加多个邮件地址,

db.users.update(
    {
        "_id":ObjectId("5ace2559f02a40eb6148fc34")
    },
    {
        "$addToSet":{
            "emails":{
                "$each”: ["xxx@xxx.com","1111@xxx.com"]
            }
        }
    }
)
删除元素

$pop修改器:从数组的任何一端删除元素

从数组末尾删除一个元素

{
    "$pop":{
        "key":1
    }
}

从数组头部删除一个元素

{
    "$pop":{
        "key":-1
    }
}

$pull:居于特定条件删除元素,而不仅仅以及元素位置

db.lists.update({},
    {
        "$pull":{
            "todo":"xxx"
        }
    }
)

删除todo等于xxx的文档

基于位置的数组修改器

若时数组中有多个值,我们只想对其中的一部分进行操作,有另种方式

  • 通过位置 增加第一个评论的投票数量
db.blog.update(
    {
        "post":post_id
    },
    {
        "$inc":{
            "commonts.0.votes:1
        }
    }
)
  • ,用来定位查询文档已经匹配的数组元素、进行隔壁服更新 要是用户john把名字改成了Jim,就可以用定位符替换他在评论中的名字
db.blog.update(
    {
        "comments.author":"john"
    },
    {
        "$set":{
            "commonts.$.votes":"jim"
        }
    }
)

upsert

是一种特殊的更新,要是没有找到符合更新条件的文档,就会以这个条件和更新文档为基础创建一个新的文档,如果找到了匹配的文档,则正常更新。

upsert非常方便,不必预制集合,同一套代码既可以用于创建文档也可以用于更新文档

记录网站页面访问次数的例子:

db.analytics.update(
    {
        "url":"/blog"
    },
    {
        "$inc":{
            "pageviews":1
        }
    },
    true
)

  • 第三个参数true表示这是个upsert
  • upsert操作时原子性的,创建文档会将条件文档作为基础,然后对他应用修改器文档
save hello 帮助程序

save时一个shell函数,如果文档不存在,它会自动创建文档,如果文档存在,它就更新这个文档,它只有一个参数,文档。要是这个文档含有_id键,save会调用upsert,否在会调用insert

更新多个文档

默认情况下,更新只能对符合匹配条件的第一个文档执行操作,要是有多个文档符合条件,只有第一个文档会呗更新。

如果要更新所有匹配的文档,可以将update的第四个参数设置为true

db.users.update({
        "brithday":"10/13/1978"
    },
    {
        "$set":{
            "gift":"happy birhday"
        }
    },
    false,
    true
)

返回呗更新的文档

findAndModift能够在一个操作中返回匹配结果并进行更新

查询

find

指定需要返回的键

有时并不需要将文档中所有键/值对都返回,可以通过find(或findOne)的第二个参数来指定想要的键。 这样可以减少传输的数据量,又能节省客户端解码文档的时间和内存消耗。

db.users.find({},{
    "username":1,
    "email":1
})

如果不指定”_od”是否返回,”_id”是默认呗返回的

既然可以选择需要的键,当然也可以排除查询结果中的某些键值对

db.users.find({},{
    "xxx":0
})

查询条件

查询条件

比较操作符:

  • $lt :<
  • $lte:<=
  • $gt:>
  • $gte:>=

例如查询“age”字段大于等于18、小于等于30的所有文档

db.users.find({
    "age":{
        "$gte":18,
        "$lte":30
    }
})

OR查询

有两种方式进行OR查询:

$in可以用于查询一个键的多个值

db.users.find({
    "user_id":{
        "$in":[123456,"joe"]
    }
})

in相反的是nin,将返回与数组中所有条件都不匹配的文档

$or可以在多个键中查询任意的给定值

db.raffle.find({
    "$or";[
        {
            "ticket_no":725
        },
        {
            "winner":true
        }
    ]
})

$not

是元条件句,可以用在任何其他条件之上,表示否定的含义

条件语义

条件语句时内层文档的键,而修改器是外层文档的键

一个键可以在任意多个条件,但是一个键不能对应多个更新修改器

特定类型的查询

null

null不仅会匹配某个键的值为null的文档,而且还会匹配不包含这个键的文档。这个匹配还会返回缺少这个键的所有文档

如果仅想匹配键值为null的文档,既要检查该键的值是否时null,还要通过$exists条件判断键值是否存在。

正则表达式

正则表达式能够有效地匹配字符串。

例如: 想要查找所有名为Joe或者joe的用户,就可以使用正则表达式执行不区分大小写的匹配

db.users.find({
    "name":/joe/i
})

  • 系统可以接受正则表达式标志(i),但不一定要有。
  • mongoDB使用Perl兼容的正则表达式来匹配正则表达式

查询数组

查询数组元素和查询标量值是一样的

例如有一个水果列表

db.food.insert({
    "fruit":["aople","banana","peach"]
})

通过下面的查询可以成功匹配到文档

db.food.find({
    "fruit":"banana"
})
$all

如果需要通过多个元素来匹配数组,就需要使用$all

  • 要找到既有apple又有banana的文档 db.food.find({ "fruit":{ "$all":["apple","banana"] } })
  • 如果不使用$all,那就是对整个数组进行精确匹配,但是精确匹配对于缺少元素或者元素沉余的情况不适用

下面将不能匹配到文档

db.food.find({
    "fruit":["apple","banana"]
})
  • 如果想查询数组特定位置的元素,需要使用key.inex语法指定下标

将数组第三个元素和peach进行匹配

db.food.find({
    "fruit.2":"peach"
})
$size

用它查询特定长度的数组。

db.food.find({
    "fruit":{
        "$size":3
    }
})

$size并不能与其他查询条件组合使用,但是这种查询可以通过在文档中添加一个“size”的值

$slice操作符

可以返回某个键匹配的数组元的一个子集

假设现在有一个博客文章的文档,我们希望返回前10条评论

db.blog.posts.findOne(criteria,{
    "comments":{
        "$slice":10
    }
})

返回后10条

db.blog.posts.findOne(criteria,{
    "comments":{
        "$slice":-10
    }
})

指定偏移量以及希望返回的元素数量,来返回元素集合中间位置的某些结果

db.blog.posts.findOne(criteria,{
    "comments":{
        "$slice":[23,10]
    }
})

除非特别声明,否则使用$slice时返回文档中的所欲键,别的键说明符都是默认返回未提及的键

返回一个匹配的数组元素

希望返回与查询条件相匹配的任意一个数组元素,可以使用$操作符得到一个匹配的元素。

用如下的方式得到Bob的评论

db.blog.posts.find({
    "comments.name":"bob"
},{
    "comments.$":1
})
数组和范围查询的相互作用

文档中的标量(非数组元素)必须与查询条件中的每一条语句相匹配

使用elemMatch要求使用查询条件中的两个语句与一个数组元素进行比较,elemMatch不会匹配非数组元素

db.test.find({
    "x":{
        "$elemMatch":{
            "$gt"10,
            "$lt":20
        }
    }
})

查询内嵌文档

有两种方法可以查询内嵌文档

  • 查询整个文档
  • 针对其键/值对进行查询

查询整个内嵌文档与普通查询完全相同,例如有如下文档

{
    "name":{
        "first":"joe",
        "last":"schmoe"
    },
    "age":45
}

要查询姓名为joe schmoe的人可以这样

db.peop;e.find({
    "name":{
        "first":"joe",
        "last":"schmoe"
    }
})

如果想要查询一个完整的子文档,那么子文档必须精确匹配,如果joe决定添加一个代表钟建明的键,那么查询就不在可行。

如果允许的话,通常只针对内嵌文档的特定键值进行查询,我们一般使用点表示法查询内嵌文档的键

db.people.find({
    "name.first":"joe",
    "name.last":"schmoe"
})

查询文档可以包含点来表达进入内嵌文档内部的意思

$where查询

为安全起见,应该严格限制或消除$where语句的使用

最常见的应用就是比较文档中的两个键的值是否相等

游标

数据库使用游标返回find的执行结果,客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者执行一些强大的操作。

limit、skip、sort

要限制结果数量,可在find后使用limit函数

db.c.find().limit(3)

要是匹配的结果不到3个,则返回匹配数量的结果。

skip和limit类似,不过时跳过前n个匹配的文档,返回余下的文档

db.c.find().skip(3)

sort接受一个独享作为参数,这个对象时一组键值对,键对应文档的键名,值代表排序的方向。排序方向可以是1(升序)或者-1(降序)

db.c.find({
    "username":1,
    "age":-1
})
比较顺序

如果混合类型的键排序,其排序顺序是预先定义好的,优先级从小到大,其顺序如下:

  1. 最小值
  2. null
  3. 数字
  4. 字符串
  5. 对象(文档)
  6. 数组
  7. 二进制数据
  8. 对象id
  9. 布尔型
  10. 日期型
  11. 时间戳
  12. 正则表达式
  13. 最大值

避免使用skip略过大量结果

  1. 不同skip对结果分页 用limit返回结果的第一页,然后每个后续页面作为相对于开始的偏移量返回
  2. 随机选取文档 在插入文档时给每个文档都添加一个额外的随机键

搞基查询选项

两种类型查询:

  • 简单查询
  • 封装查询

用于向查询中添加各种选项:

  • $maxscan : integer 指定本次扫描中扫描文档数量的上限
  • $min: document 查询的开始条件,在这样的查询中,文档必须与索引的键完全匹配
  • $max: document 查询的结束条件,在这样的查询中,文档必须与索引的键完全匹配

注:

  • 上述测试在MongoDB 3.4.3-8-g05b19c6中成功
  • 上述文字皆为个人看法,如有错误或建议请及时联系我

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MongoDB权威指南学习笔记(2)--设计应用

    索引的值是按照一定顺序排列的,因此,使用索引键对文档进行排序非常快。然而,只有在首先使用索引键进行排序时,索引才有用。

    用户1637228
  • spring中的SpEL表达式

    Spring 3引入了Spring表达式语言( Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean...

    用户1637228
  • java中的阻塞队列

    阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线...

    用户1637228
  • Learning to Rank 小结

    一、学习排序(Learning to Rank) LTR(Learning torank)学习排序是一种监督学习(SupervisedLearnin...

    智能算法
  • Indesign怎么创建单排排列的文档?

    Indesign中想要创建单排排列的文档,该怎么创建呢?下面我们就来看看详细的教程。

    砸漏
  • Excelize 2.3.1 发布,Go 语言 Excel 文档基础库,支持加密表格文档

    Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来...

    xuri
  • Windows下优雅地书写MarkDown

    自从将博客搬到Hexo之后,书写MarkDown文档的频率就大大提高了,在享受着免排版的语法优势的同时又深深地受插入图片所困扰。找图床、加链接,大大降低了写文档...

    信安之路
  • 【Matlab机器学习】用Matlab编写的文本分类程序

    特征提取步骤 1. 卡方检验 1.1 统计样本集中文档总数(N)。 1.2 统计每个词的正文档出现频率(A)、负文档出现频率(B)、正文档不出现频率)、负文...

    量化投资与机器学习微信公众号
  • [翻译]WebSocket协议——摘要

    本系列内容为RFC6455 WebSocket协议的中文翻译版。进行相关文档规范的翻译初衷是为了更加深刻的了解WebSocket以及相关内容。

    黄Java
  • 解除Word文档的编辑锁定

    有时候从某些网站上导出的一些文档,由于它里面的正则表达式或者兼容性的问题会造成格式略微有点问题,而这些文档又被限制编辑,根本没法二次微调修改,下面就演示一下我从...

    啤酒单恋小龙虾

扫码关注云+社区

领取腾讯云代金券