与传统关系数据库 (RDBMS/sql) 相比, NoSQL数据库结构受限较少、可扩展的模式设计以及访问速度更快,因此在过去几年中出现了巨大的发展。mongodb是一个开源的、面向文档的 NoSQL 数据库,它以类 JSON 对象的形式存储数据。由于其动态模式、高可扩展性、最佳查询性能、更快的索引和活跃的用户社区,它已成为领先的数据库之一。在本教程中,我们将使用 MongoDB 作为 NoSQL 数据库的示例。
如果您来自 RDBMS/SQL 背景,那么在开始时理解 NoSQL 和 MongoDB 概念可能会有点困难,因为这些技术具有非常不同的数据表示方式。本文将帮助您了解 RDBMS/SQL 域的工作原理,以及它的功能、术语和查询语言如何映射到 MongoDB 数据库。通过映射,我的意思是如果我们在 RDBMS/SQL 中有一个概念,我们将看到它在 MongoDB 中的等效概念是什么。
我们将从映射基本的关系概念开始,如表、行、列等,然后我们将讨论索引和连接。然后我们将查看一些 SQL 查询并讨论它们相应的 MongoDB 数据库查询。本文假设您了解基本的关系数据库概念和 SQL,因为整篇文章将更多地强调理解这些概念在 MongoDB 中的映射方式。让我们开始。
映射表、行和列
MongoDB中的每个数据库都是由集合组成的,相当于一个由SQL表组成的RDBMS数据库。每个集合以文档的形式存储数据,相当于表以行的形式存储数据。虽然行将数据存储在其列集中,但文档具有类似 JSON 的结构(在 MongoDB 中称为 BSON)。最后,我们在 SQL 行中有行,而我们在 MongoDB 中有字段。下面是一个文档(SQL 中的行)的示例,该文档具有一些字段(SQL 中的列),在 MongoDB 中存储用户数据:
{ "_id": ObjectId("5146bb52d8524270060001f3"), "age": 25,"city": "Los Angeles", "email": "mark@abc.com", "user_name": "Mark Hanks" }
该文档相当于 RDBMS 中的单行。一个集合由许多这样的文档组成,就像一个表由许多行组成一样。请注意,集合中的每个文档都有一个唯一_id字段,这是一个 12 字节的字段,用作文档的主键。该字段在创建文档时自动生成,用于唯一标识每个文档。
users为了更好地理解映射,让我们以 MongoDB 中的 SQL 表及其对应结构为例。如图 1 所示,SQL 表中的每一行对应一个文档,每一列对应 MongoDB 中的一个字段。
动态架构
这里要关注的一件有趣的事情是集合中的不同文档可以有不同的模式。所以在 MongoDB 中一个文档有五个字段而另一个文档有七个字段是可能的。这些字段可以随时轻松添加、删除和修改。此外,对字段的数据类型没有限制。因此,在一个实例中,一个字段可以包含一种int数据类型,而在下一个实例中,它可能包含一种array数据类型。
如果您来自 RDBMS 背景,其中表结构及其列、数据类型和关系是预定义的,那么这些概念可能看起来非常不同。这种使用动态模式的功能允许我们在运行时生成动态文档。
例如,考虑同一集合中的以下两个文档但具有不同的架构(图 2):
第一个文档包含第二个文档中不存在的字段address和dob,而第二个文档包含第一个文档中不存在的字段gender和occupation。如果我们必须在 SQL 中设计此模式,我们会为address、dob、gender和保留四个额外的列occupation,其中一些将存储空(或 null)值,因此占用不必要的空间。
这种动态模式模型是 NoSQL 数据库在设计方面具有高度可扩展性的原因。使用此类文档可以有效地设计需要大量 RDBMS 表的各种复杂模式(分层、树结构等)。
一个典型的例子是以文档的形式存储用户的帖子、喜欢、评论和其他相关信息。上述设计模式的 SQL 实现最好有单独的表来存储帖子、评论和点赞,而 MongoDB 文档会将所有这些信息存储在一个文档中。
映射联接和关系
RDBMS 中的关系是通过使用主键和外键关系并使用连接查询这些关系来实现的。MongoDB 中没有这种直接的映射,但是这里的关系是使用嵌入和链接文档设计的。
考虑一个例子,我们需要存储用户信息和相应的联系信息。理想的 SQL 设计应该有两个表,比如user_information和contact_information,主键id和contact_id,如图 3 所示。该contact_information表还包含一个列user_id,它是链接到表id字段的外键user_information。
现在我们将看到如何使用链接文档和嵌入文档的方法在 MongoDB 中设计这种关系。请注意,在 SQL 模式中,我们通常会添加一个列(id在contact_id我们的例子中)作为该表的主列。但是在MongoDB中,我们一般使用自动生成的_id字段作为主键来唯一标识文档。
链接文档
这种方法将使用两个集合,user_information和contact_information,它们都有自己独特的_id领域。我们将user_id在contact_information文档中有一个字段与文档的_id字段相关user_information,显示联系人对应于哪个用户(见图 4)。重要的是要注意,在 MongoDB 中,关系及其相应的操作通常是手动处理的(例如,通过代码),因为没有外键约束和规则适用。
我们文档中的user_id字段只是一个保存一些数据的字段,我们必须实现与之相关的所有逻辑。例如,即使您在集合中不存在user_id的文档中插入 a ,MongoDB 也不会抛出任何错误,指出在集合中找不到相应的(与 SQL 不同,这将是一个无效的外键约束)。contact_information user_informationuser_iduser_information
嵌入文档
第二种方法是像这样将文档嵌入contact_information文档内部user_information(图 5):
在上面的示例中,我们在用户信息中嵌入了一个联系信息的小文档。以类似的方式,可以像这样嵌入大型复杂文档和分层数据以关联实体。
此外,使用的方法取决于设计的具体场景。如果预计要嵌入的数据变大,最好使用链接方式而不是嵌入方式,以避免文档变得太大。嵌入式方法通常用于必须嵌入有限数量信息(如我们示例中的地址)的情况。
映射图
总而言之,下图(图 6)代表了我们讨论过的常见相互关系:
将 SQL 映射到 MongoDB 查询
现在我们已经熟悉了 RDBMS 和 MongoDB 之间的基本映射,我们将讨论用于与数据库交互的查询语言在它们之间有何不同。
对于 MongoDB 查询,我们假设users一个文档结构如下的集合:
{ "_id": ObjectId("5146bb52d8524270060001f3"), "post_text":"This is a sample post" , "user_name": "mark", "post_privacy": "public", "post_likes_count": 0 }
对于 SQL 查询,我们假设表users有五列,结构如下:
我们将讨论与创建和更改集合(或表)以及插入、读取、更新和删除文档(或行)相关的查询。每个点有两个查询,一个针对 SQL,另一个针对 MongoDB。我将仅解释 MongoDB 查询,因为我们对 SQL 查询非常熟悉。此处介绍的 MongoDB 查询是在 Mongo javascript shell 中编写的,而 SQL 查询是在 mysql 中编写的。
创造
在 MongoDB 中,不需要显式创建集合结构(就像我们使用CREATE TABLE查询对表所做的那样)。当第一个插入发生在集合中时,自动创建文档的结构。createCollection但是,您可以使用该命令创建一个空集合。
SQL: CREATE TABLE `posts` (`id` int(11) NOT NULL AUTO_INCREMENT,`post_text` varchar(500) NOT NULL,`user_name` varchar(20) NOT NULL,`post_privacy` varchar(10) NOT NULL,`post_likes_count` int(11) NOT NULL,PRIMARY KEY (`id`)) MongoDB: db.createCollection("posts")
插入
要在 MongoDB 中插入文档,我们使用insertOneorinsertMany方法,该方法将具有键值对的对象作为其输入。该insertOne方法插入单个文档,同时insertMany插入多个文档。插入的文档将包含自动生成的_id字段。_id但是,您也可以与其他字段一起显式提供 12 字节的值。
SQL: INSERT INTO `posts` (`id` ,`post_text` ,`user_name` ,`post_privacy` ,`post_likes_count`)VALUES (NULL , 'This is a sample post', 'mark', 'public', '0'); MongoDB: db.posts.insertOne({user_name:"mark", post_text:"This is a sample post", post_privacy:"public", post_likes_count:0})
需要注意的是Alter Table,MongoDB 中没有更改文档结构的功能。由于文档在架构中是动态的,因此架构会在文档更新时发生变化。
读
MongoDB使用的find方法,相当于SELECTSQL中的command。以下语句只是读取posts集合中的所有文档。
SQL: SELECT * FROM `posts` MongoDB: db.posts.find()
以下查询对user_name字段为 的文档进行条件搜索mark。获取文档的所有条件都必须放在第一个大括号 {} 中,用逗号分隔。
SQL: SELECT * FROM `posts` WHERE `user_name` = 'mark' MongoDB: db.posts.find({user_name:"mark"})
以下查询获取特定列,post_text并且post_likes_count, 如第二组大括号 {} 中所指定。
SQL: SELECT `post_text` , `post_likes_count` FROM `posts` MongoDB: db.posts.find({},{post_text:1,post_likes_count:1})
请注意,默认情况下,MongoDB 会在_id每个查找语句中返回该字段。如果我们不希望结果集中出现该字段,则必须在要检索的列列表中指定_id具有值的键。0键的0值表示我们要从结果集中排除该字段。
MongoDB: db.posts.find({},{post_text:1,post_likes_count:1,_id:0})
以下查询根据 的条件提取特定user_name字段mark。
SQL: SELECT `post_text` , `post_likes_count` FROM `posts` WHERE `user_name` = 'mark' MongoDB: db.posts.find({user_name:"mark"},{post_text:1,post_likes_count:1})
我们现在将再添加一个条件来获取隐私类型为公开的帖子。使用逗号指定的条件字段表示逻辑AND条件。因此,此语句将查找同时具有user_nameasmark和post_privacyas的文档public。
SQL: SELECT `post_text` , `post_likes_count` FROM `posts` WHERE `user_name` = 'mark' AND `post_privacy` = 'public' MongoDB: db.posts.find({user_name:"mark",post_privacy:"public"},{post_text:1,post_likes_count:1})
OR要在方法中的条件之间使用逻辑find,我们使用$or运算符。
SQL: SELECT `post_text` , `post_likes_count` FROM `posts` WHERE `user_name` = 'mark' OR `post_privacy` = 'public' MongoDB: db.posts.find({$or:[{user_name:"mark"},{post_privacy:"public"}]},{post_text:1,post_likes_count:1})
接下来,我们将使用该方法,该方法将结果按(以 表示)sort的升序排序。post_likes_count1
SQL: SELECT * FROM `posts` WHERE `user_name` = 'mark' order by post_likes_count ASC MongoDB: db.posts.find({user_name:"mark"}).sort({post_likes_count:1})
要按降序对结果进行排序,我们-1将字段的值指定为。
SQL: SELECT * FROM `posts` WHERE `user_name` = 'mark' order by post_likes_count DESC MongoDB: db.posts.find({user_name:"mark"}).sort({post_likes_count:-1})
为了限制要返回的文档数,我们使用limit方法,指定文档数。下面的查询将只获取 10 个文档作为结果。
SQL: SELECT * FROM `posts` LIMIT 10 MongoDB: db.posts.find().limit(10)
与我们offset在 SQL 中使用的跳过多条记录的方式相同,我们可以使用skipMongoDB 中的函数。例如,以下语句将获取十个帖子,跳过前五个。
SQL: SELECT * FROM `posts` LIMIT 10 OFFSET 5 MongoDB: db.posts.find().limit(10).skip(5)
更新
有两种方法用于更新文档:updateOne用于更新单个文档,而updateMany用于更新多个文档。该updateOne方法的第一个参数指定了选择文档的标准。第二个参数指定要执行的实际更新操作。例如,以下查询选择所有带有user_nameas的文档mark并将它们设置post_privacy 为private。
这里的一个区别是,默认情况下,MongoDBupdate查询只更新一个(和第一个匹配的)文档。要更新所有匹配的文档,我们必须提供第三个参数指定multi为true,表示我们要更新多个文档。
SQL: UPDATE posts SET post_privacy = "private" WHERE user_name='mark' MongoDB: db.posts.updateOne({user_name:"mark"},{$set:{post_privacy:"private"}})
消除
删除文档非常简单,类似于 SQL。该查询删除用户名为 Mark 的帖子的所有条目。
SQL: DELETE FROM posts WHERE user_name='mark' MongoDB: db.posts.deleteOne({user_name:"mark"})
索引
MongoDB 在_id每个集合的字段上创建了一个默认索引。要在字段上创建新索引,我们使用ensureIndex方法,指定字段和关联的排序顺序,由1或-1(升序或降序)表示。
SQL: CREATE INDEX index_posts ON posts(user_name,post_likes_count DESC) MongoDB: db.posts.ensureIndex({user_name:1,post_likes_count:-1})
要查看任何集合中存在的所有索引,我们在SQL 查询getIndexes的同一行上使用该方法。SHOW INDEX
SQL: SHOW INDEX FROM posts MongoDB: db.posts.getIndexes()
结论
在本文中,我以 MongoDB 为例,解释了 RDBMS/SQL 的基本概念和术语在 NoSQL 数据库中的关系。我们研究了 MongoDB 中的关系设计,了解了基本 SQL 查询的功能如何映射到 MongoDB 中。
阅读本文后,您可以继续尝试复杂的查询,包括聚合、map reduce 和涉及多个集合的查询。您还可以使用一些在线工具在开始时将 SQL 查询转换为 MongoDB 查询。您可以尝试自己设计一个示例 MongoDB 数据库模式。这样做的最佳示例之一是用于存储用户帖子、点赞、评论和评论点赞的数据库。这将使您对 MongoDB 提供的灵活模式设计有一个实际的了解。
- 动态架构
- 链接文档
- 嵌入文档
- 创造
- 插入
- 读
- 更新
- 消除
- 索引
发表评论