MongoDB 是一种面向文档的数据库,而不是关系型数据库。远离关系模型的主要原因是使扩展更容易,但也有一些其他优点。面向文档的数据库将“行”的概念替换为更灵活的模型“文档”。通过允许嵌入文档和数组,面向文档的方法可以用单个记录表示复杂的层次关系。这自然符合现代面向对象语言的开发人员思考数据的方式。也没有预定义的模式:文档的键和值不是固定的类型或大小。如果没有固定架构,根据需要添加或删除字段会变得更容易。通常,这会使开发速度更快,因为开发人员可以快速迭代。它也更容易进行实验。开发人员可以尝试数十种数据模型,然后选择最好的一种进行追求。以下是使用数据库的一些优点:

  • 易于扩展

  • 支持大量的功能,除了创建、更新、读取和删除之外,它还提供了独特的功能,如索引、聚合、特殊集合数据类型、文件存储。

  • 速度很快

文件

MongoDB 的核心是文档:一组有序的键和关联的值。文档的表示方式因编程语言而异,但大多数语言都有一种自然适合的数据结构,例如映射、散列或字典。文档是 MongoDB 的基本数据单元,大致相当于关系数据库管理系统中的一行(但更具表现力)。例如:

{"greeting" : "Hello, world!"}

这个简单的文档包含一个键“greeting”,其值为“Hello, world!”。大多数文档会比这个简单的文档更复杂,并且通常会包含多个键/值对。

{"greeting" : "Hello, world!", "foo" : 3}

命名密钥

文档中的键是字符串。密钥中允许使用任何 UTF-8 字符,但有一些值得注意的例外:

  • 键不能包含字符\0(空字符)。该字符用于表示键的结束。

  • .和 $ 字符有一些特殊的属性,应该只在某些情况下使用,如后面章节中所述。一般来说,它们应该被认为是保留的,如果使用不当,司机会抱怨。 MongoDB 是类型敏感和大小写敏感的。例如,这些文档是不同的:

{"foo" : 3} 
{"foo" : "3"}
{"foo" : 3}
{"Foo" : 3}

最后需要注意的重要一点是 MongoDB 中的文档不能包含重复的键。

合集

集合是一组文档。如果文档是关系数据库中行的 MongoDB 类比,那么集合可以被认为是表的类比。集合具有动态模式。这意味着单个集合中的文档可以具有任意数量的不同“形状”。

命名集合

集合由其名称标识。集合名称可以是任何 UTF-8 字符串,但有一些限制:

  • 空字符串 ("") 不是有效的集合名称。

  • 集合名称可能不包含字符\0(空字符),因为它描述了集合名称的结尾。

  • 您不应该创建任何以 system. 开头的集合,这是为内部集合保留的前缀。例如,system.users 集合包含数据库的用户,system.namespaces 集合包含有关所有数据库集合的信息。

  • 用户创建的集合名称中不应包含保留字符$。数据库可用的各种驱动程序确实支持在集合名称中使用 $,因为某些系统生成的集合包含它。除非您访问这些集合之一,否则不应在名称中使用 $。

数据库

除了按集合对文档进行分组外,MongoDB 还将集合分组到数据库中。单个 MongoDB 实例可以托管多个数据库,每个数据库将零个或多个集合组合在一起。数据库有自己的权限,每个数据库都存储在磁盘上的单独文件中。一个好的经验法则是将单个应用程序的所有数据存储在同一个数据库中。在同一 MongoDB 服务器上存储多个应用程序或用户的数据时,单独的数据库很有用。

命名数据库

  • 空字符串 ("") 不是有效的数据库名称。

  • 数据库名称不能包含以下任何字符:/、\、.、"、*、<、>、:、|、?、$、(单个空格)或 \0(空字符)。基本上,坚持使用字母数字 ASCII。

  • 数据库名称区分大小写,即使在不区分大小写的文件系统上也是如此。为简单起见,请尝试仅使用小写字符。

  • 数据库名称被限制为最多 64 个字节。

MongoDB入门

MongoDB 几乎总是作为客户端可以连接并执行操作的网络服务器运行。下载 MongoDB 并解压缩。要启动服务器,请运行 mongod 可执行文件:

mongod #starts the deamon version of mongodb

运行Shell

要启动 shell,请运行可执行文件:

mongo #displays the version of the mongo you have installed on your system

基本操作与Shell

创建

insert 函数将文档添加到集合中。例如,假设我们要存储一篇博客文章。首先,我们将创建一个名为 post 的局部变量,它是一个表示我们文档的 JavaScript 对象。它将具有键“标题”、“内容”和“日期”(发布日期)。

post = {"title" : "My Blog Post",
... "content" : "Here's my blog post.",
... "date" : new Date()}
{
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2012-08-24T21:12:09.982Z") }

db.blog.insert(post)

博客文章已保存到数据库中。我们可以通过在集合上调用 find 来查看它:

db.blog.find()
{
"_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2012-08-24T21:12:09.982Z") }

find 和 findOne 可用于查询集合。如果我们只想查看集合中的一个文档,我们可以使用 findOne。

db.blog.findOne()
{
"_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2012-08-24T21:12:09.982Z") }

find 和 findOne 也可以以查询文档的形式传递标准。这将限制查询匹配的文档。 shell 将自动显示最多 20 个与查找匹配的文档,但可以获取更多。

更新

如果我们想修改我们的帖子,我们可以使用更新。 update 需要(至少)两个参数:第一个是查找要更新的文档的标准,第二个是新文档。假设我们决定对我们之前创建的博客文章启用评论。

post.comments = []
db.blog.update({title : "My Blog Post"}, post)

输出

db.blog.find()
{
"_id" : ObjectId("5037ee4a1084eb3ffeef7228"),
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2012-08-24T21:12:09.982Z"),
"comments" : [ ] }

删除

remove 从数据库中永久删除文档。不带参数调用,它从集合中删除所有文档。它还可以采用指定删除标准的文档。例如,这将删除我们刚刚创建的帖子:

db.blog.remove({title : "My Blog Post"})

数据类型

null Null 可用于表示空值和不存在的字段:

{"x" : null}

boolean 有一个布尔类型,可用于值 true 和 false:

{"x" : true}

number shell 默认使用 64 位浮点数。因此,这些数字在 shell 中看起来“正常”:

{"x" : 3.14}
or:
{"x" : 3}

对于整数,使用 NumberInt 或 NumberLong 类,它们分别表示 4 字节或 8 字节有符号整数。

{"x" : NumberInt("3")}
{"x" : NumberLong("3")}

string 任何 UTF-8 字符的字符串都可以使用字符串类型表示:

{"x" : "foobar"}

date 日期存储为自纪元以来的毫秒数。不存储时区:

{"x" : new Date()}

_正则表达式_查询可以使用 JavaScript 的正则表达式语法使用正则表达式:

{"x" : /foobar/i}

array 值的集合或列表可以表示为数组:

{"x" : ["a", "b", "c"]}

embedded document 文档可以包含作为值嵌入到父文档中的整个文档:{"x" : {"foo" : "bar"}}

创建文档

插入是向 MongoDB 添加数据的基本方法。要将文档插入到集合中,请使用集合的 insert 方法:

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

批量插入

如果您有将多个文档插入到集合中的情况,您可以使用批量插入来加快插入速度。批量插入允许您将文档数组传递给数据库。

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

输出

db.foo.find()
{ "_id" : 0 } 
{ "_id" : 1 } 
{ "_id" : 2 }

删除文档

删除数据库的记录。

db.foo.remove() #removes all records

remove 函数可以选择将查询文档作为参数。当它给出时,只有符合条件的文档才会被删除。例如,假设我们要从 mailing.list 集合中删除“optout”值为 true 的所有人:

db.mailing.list.remove({"opt-out" : true})

更新文档

一旦文档存储在数据库中,就可以使用更新方法对其进行更改。 update 有两个参数:一个查询文档,它定位要更新的文档,以及一个修饰文档,它描述对找到的文档所做的更改。

joe = db.people.findOne({"name" : "joe", "age" : 20})
db.people.update({"_id" : ObjectId("4b2b9f67a1f631733d917a7c")}, joe)

使用 $set 修饰符

$set 设置字段的值。如果该字段尚不存在,则会创建该字段。这对于更新架构或添加用户定义的键非常方便。

 db.users.findOne()
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"name" : "joe",
"age" : 30,
"sex" : "male",
"location" : "Wisconsin"
}

db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")},
... {"$set" : {"favorite book" : "War and Peace"}})

输出

 db.users.findOne()
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"name" : "joe",
"age" : 30,
"sex" : "male",
"location" : "Wisconsin",
"favorite book" : "War and Peace"
}

如果用户意识到他实际上不喜欢阅读,他可以使用 "$unset" 完全删除密钥:

db.users.update({"name" : "joe"},
... {"$unset" : {"favorite book" : 1}})

递增和递减

"$inc" 修饰符可用于更改现有键的值或创建新键(如果它不存在)。它对于更新分析、业力、投票或任何其他具有可变数值的东西非常有用。

db.games.insert({"game" : "pinball", "user" : "joe"})

db.games.update({"game" : "pinball", "user" : "joe"},
... {"$inc" : {"score" : 50}}) #increases the score by 50

输出

db.games.findOne()
{
"_id" : ObjectId("4b2d75476cc613d5ee930164"),
"game" : "pinball",
"user" : "joe",
"score" : 50
}

添加元素

"$push" 如果数组存在则将元素添加到数组的末尾,如果不存在则创建一个新数组。例如,假设我们正在存储博客文章并想要添加一个包含数组的“comments”键。我们可以将评论推送到不存在的“评论”数组中,这将创建数组并添加评论。

 db.blog.posts.findOne()
{
"_id" : ObjectId("4b2d75476cc613d5ee930164"),
Updating Documents | 37
"title" : "A blog post",
"content" : "..."
}> db.blog.posts.update({"title" : "A blog post"},
... {"$push" : {"comments" :
... {"name" : "joe", "email" : "joe@example.com",
... "content" : "nice post."}}})

输出

 db.blog.posts.findOne()
{
"_id" : ObjectId("4b2d75476cc613d5ee930164"),
"title" : "A blog post",
"content" : "...",
"comments" : [ {
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
} ] }

移除元素

有几种方法可以从数组中删除元素。如果要将数组视为队列或堆栈,可以使用“$pop”,它可以从任一端删除元素。 {"$pop" : {"key" : 1}} 从数组末尾删除一个元素。 {"$pop" :{"key" : -1}} 从头删除它。有时应根据特定标准删除元素,而不是其在数组中的位置。 "$pull" 用于删除符合给定条件的数组元素。例如,假设我们有一个需要完成但没有按任何特定顺序完成的事情的列表。

db.lists.insert({"todo" : ["dishes", "laundry", "dry cleaning"]})
db.lists.update({}, {"$pull" : {"todo" : "laundry"}})

输出

db.lists.find()
{
"_id" : ObjectId("4b2d75476cc613d5ee930164"),
"todo" : [
"dishes",
"dry cleaning"
] }

查询中

查找方法

find 方法用于在 MongoDB 中执行查询。查询返回集合中文档的子集,从根本没有文档到整个集合。返回哪些文档由 find 的第一个参数确定,该参数是指定查询条件的文档。一个空的查询文档(即 {})匹配集合中的所有内容。如果 find 未提供查询文档,则默认为 {}。例如:

db.c.find() #matches every document in the collection c

当我们开始向查询文档添加键/值对时,我们开始限制我们的搜索。例如,要查找“age”值为 27 的所有文档,我们可以将该键/值对添加到查询文档中:

 db.users.find({"age" : 27}) #returns all the users with age 27

db.users.find({"username" : "joe"}) #returns all the users with username Joe

db.users.find({"username" : "joe", "age" : 27}) #returns all the records of username Joe and age , 27

如果您有一个用户集合并且您只对“用户名”和“电子邮件”键感兴趣,则可以使用以下查询仅返回这些键:

 db.users.find({}, {"username" : 1, "email" : 1}) #returns username and email only
or
 db.users.find({}, {"username" : 1, "_id" : 0}) #returns the username but not the id

输出

{
"_id" : ObjectId("4ba0f0dfd22aa494fd523620"),
"username" : "joe",
"email" : "joe@example.com"
}

{
"username" : "joe", }

查询条件

“$lt”、“$lte”、“$gt”、“$gte”都是比较运算符,分别对应<、<u003d、>、>u003d。可以将它们组合起来以查找一系列值。例如,要查找年龄在 18 到 30 岁之间的用户,我们可以这样做:

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

这将找到“年龄”字段大于或等于 18 且小于或等于 30 的所有文档。这些类型的范围查询通常对日期有用。例如,要查找在 2007 年 1 月 1 日之前注册的人,我们可以这样做:

start = new Date("01/01/2007")
db.users.find({"registered" : {"$lt" : start}})

要查询某个键的值不等于某个值的文档,您必须使用另一个条件运算符“$ne”,它代表“不等于”。如果您想查找所有没有用户名“joe”的用户,您可以使用以下命令查询他们:

db.users.find({"username" : {"$ne" : "joe"}})

"$ne" 可以用于任何类型。

OR 查询

在 MongoDB 中执行 OR 查询有两种方法。 "$in" 可用于查询单个键的各种值。 "$or" 更通用;它可用于跨多个键查询任何给定值。如果您有多个可能的值与单个键匹配,请使用带有“$in”的条件数组。例如,假设我们在进行抽奖,中奖号码是 725、542 和 390。要查找所有这三个文档,我们可以构造以下查询:

db.raffle.find({"ticket_no" : {"$in" : [725, 542, 390]}})

"$in" 非常灵活,允许您指定不同类型的标准以及值。例如,如果我们正在逐步迁移我们的模式以使用用户名而不是用户 ID 号,我们可以使用以下方式查询:

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

这将匹配 "user_id" 等于 12345 的文档,以及 "user_id" 等于 "Joe" 的文档。 "$in" 的反面是 "$nin",它返回不匹配的文档数组中的任何条件。如果我们想返回所有在抽奖中没有中奖的人,我们可以用这个来查询他们。

db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})

此查询返回没有带有这些号码的票的每个人。 "$in" 为您提供单个键的 OR 查询,但是如果我们需要查找 "ticket_no" 为 725 或 "winner" 为真的文档怎么办?对于这种类型的查询,我们需要使用“$or”条件。 "$or" 采用一系列可能的标准。在抽奖的情况下,使用 "$or" 看起来像这样:

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

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

$不

"$not" 是一个元条件:它可以应用在任何其他标准之上。例如,让我们考虑模运算符“$mod”。 "$mod" 查询其值除以给定的第一个值时具有第二个值的余数的键:

db.users.find({"id_num" : {"$mod" : [5, 1]}})

上一个查询返回“id_num”为 1、6、11、16 等的用户。相反,如果我们想要返回“id_num”为 2、3、4、5、7、8、9、10、12 等的用户,我们可以使用“$not”:

db.users.find({"id_num" : {"$not" : {"$mod" : [5, 1]}}})

"$not" 在与正则表达式结合查找与给定模式不匹配的所有文档时特别有用。

条件语义

多个条件可以放在一个键上。例如,要查找年龄在 20 到 30 岁之间的所有用户,我们可以在 "age" 键上同时查询 "$gt" 和 "$lt":

db.users.find({"age" : {"$lt" : 30, "$gt" : 20}})

外部文档中有一些“元运算符”:“$and”、“$or”和“$nor”。它们都有类似的形式:

db.users.find({"$and" : [{"x" : {"$lt" : 1}}, {"x" : 4}]})

此查询将匹配具有小于 1 且等于 4 的“x”字段的文档。虽然这些看起来是矛盾的条件,但如果“x”字段是一个数组,则可以满足:{“x”:[0 , 4]} 将匹配。请注意,查询优化器不会优化“$and”以及其他运算符。此查询的结构将更有效:

db.users.find({"x" : {"$lt" : 1, "$in" : [4]}})

正则表达式

正则表达式对于灵活的字符串匹配很有用。例如,如果我们要查找名称为 Joe 或 joe 的所有用户,我们可以使用正则表达式进行不区分大小写的匹配:

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

查询数组

查询数组元素的设计方式与查询标量的方式相同。例如,如果数组是水果列表,如下所示:

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

以下查询:

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

将成功匹配文档。如果我们有一个看起来像(非法)文档的文档,我们可以以几乎相同的方式查询它:{“fruit”:“apple”,“fruit”:“banana”,“fruit”:“peach “}。

$全部

如果需要通过多个元素匹配数组,可以使用“$all”。这允许您匹配元素列表。例如,假设我们创建了一个包含三个元素的集合:

db.food.insert({"_id" : 1, "fruit" : ["apple", "banana", "peach"]})
db.food.insert({"_id" : 2, "fruit" : ["apple", "kumquat", "orange"]})
db.food.insert({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})

通过使用 "$all" 查询找到所有包含 "apple" 和 "banana" 元素的文档

db.food.find({fruit : {$all : ["apple", "banana"]}})

输出

{"_id" : 1, "fruit" : ["apple", "banana", "peach"]}
{"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}

您还可以使用整个数组通过精确匹配进行查询。但是,如果任何元素丢失或多余,完全匹配将不会匹配文档。例如,这将匹配上面的第一个文档:

db.food.find({"fruit" : ["apple", "banana", "peach"]})

但这不会:

db.food.find({"fruit" : ["apple", "banana"]})

这也不会:

db.food.find({"fruit" : ["banana", "apple", "peach"]})

如果要查询数组的特定元素,可以使用语法 key.index 指定索引:

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

数组总是从 0 开始索引,因此这将匹配第三个数组元素与字符串“peach”。

$大小

查询数组的一个有用条件是“$size”,它允许您查询给定大小的数组。这是一个例子:

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

一个常见的查询是获取一系列大小。 "$size" 不能与另一个 $ 条件组合(在本例中为 "$gt"),但可以通过向文档添加 "size" 键来完成此查询。然后,每次向数组中添加元素时,都会增加“size”的值。如果原始更新如下所示:

db.food.update(criteria, {"$push" : {"fruit" : "strawberry"}})

它可以简单地更改为:

db.food.update(criteria,
... {"$push" : {"fruit" : "strawberry"}, "$inc" : {"size" : 1}})

$slice 操作符

特殊的“$slice”运算符可用于返回数组键的元素子集。例如,假设我们有一个博客文章文档,我们想要返回前 10 条评论:

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

或者,如果我们想要最后 10 条评论,我们可以使用 -10:

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

"$slice" 还可以通过获取偏移量和要返回的元素数量来返回结果中间的页面:

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

这将跳过前 23 个元素并返回第 24 到第 33 个。如果数组中的元素少于 33 个,它将返回尽可能多的元素。

返回匹配的数组元素

当您知道元素的索引时,“$slice”会很有帮助,但有时您希望任何一个数组元素都符合您的条件。您可以使用 $-operator 返回匹配的元素。鉴于上面的博客示例,您可以通过以下方式获得 Bob 的评论:

db.blog.posts.find({"comments.name" : "bob"}, {"comments.$" : 1})

输出

{
"_id" : ObjectId("4b2d75476cc613d5ee930164"),
"comments" : [ {
"name" : "bob",
"email" : "bob@example.com",
"content" : "good post."
} ] }

查询内嵌文档

有两种查询嵌入文档的方法:查询整个文档或查询其单独的键/值对。查询整个嵌入文档的工作方式与普通查询相同。例如,如果我们有一个如下所示的文档:

{
"name" : {
"first" : "Joe",
"last" : "Schmoe"
},
"age" : 45
}

我们可以使用以下内容查询名为 Joe Schmoe 的人:

db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}})

db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"})

限制、跳过和排序

最常见的查询选项是限制返回的结果数量、跳过一些结果和排序。在将查询发送到数据库之前,必须添加所有这些选项。要设置限制,请将限制函数链接到您的查找调用。例如,要仅返回三个结果,请使用以下命令:

db.c.find().limit(3) #returns the first 3 records

跳过的工作方式类似于限制

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

这将跳过前三个匹配的文档并返回其余的匹配项。如果您的集合中的文档少于三个,它将不会返回任何文档。 sort 接受一个对象:一组键/值对,其中键是键名,值是排序方向。排序方向可以是 1(升序)或 -1(降序)。如果给出多个键,结果将按该顺序排序。例如,要按“用户名”升序和“年龄”降序对结果进行排序,我们执行以下操作:

db.c.find().sort({username : 1, age : -1})

这三种方法可以结合使用。这对于分页通常很方便。例如,假设您正在经营一家在线商店并且有人搜索 mp3。如果您希望每页按价格从高到低排序 50 个结果,您可以执行以下操作:

db.stock.find({"desc" : "mp3"}).limit(50).sort({"price" : -1})

如果该人单击下一页以查看更多结果,您可以简单地在查询中添加一个跳过,这将跳过前 50 个匹配项(用户已经在第 1 页看到):

db.stock.find({"desc" : "mp3"}).limit(50).skip(50).sort({"price" : -1})

比较顺序

1.最小值

  1. 数字(整数、长整数、双精度数)

  2. 弦乐

  3. 对象/文件

  4. 数组

  5. 二进制数据

  6. 对象 ID

  7. 布尔值

  8. 日期

  9. 时间戳

12.正则表达式

13.最大值

数据库命令

有一种非常特殊的查询类型称为数据库命令。我们已经介绍了创建、更新、删除和查找文档。数据库命令执行“其他所有操作”,从关闭服务器和克隆数据库等管理任务到对集合中的文档进行计数和执行聚合。整本书都提到了命令,因为它们对于数据操作、管理和监控很有用。例如,删除一个集合是通过“drop”数据库命令完成的:

db.runCommand({"drop" : "test"})

db.test.drop()

输出

{
"nIndexesWas" : 1,
"msg" : "indexes dropped for collection",
"ns" : "test.test",
"ok" : true
}
Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐