Chapter02 Setting Up the Document Store with MongoDB
09 MongoDB querying and CRUD operations
FastAPI+React全栈开发09 MongoDB的增删改查操作
After all this setting up, downloading, and installing, it is finally time to see MongoDB in action and try to get what all the fuss is about. In this section, we will show, through some simple examples, the nmost essential MongoDB commands. Though simple, these methods will enable us, the developers, to take control of our data, create new documents, query documents by using different criteria and conditions, perform simple and more complex aggregations, and output data in various forms. You might say that the real fun begins here!
在所有这些设置、下载和安装之后,最后是时候看看MongoDB的实际运行,并尝试了解所有这些大惊小怪的事情。在本节中,我们将通过一些简单的示例展示最基本的MongoDB命令。虽然简单,但这些方法将使我们开发人员能够控制我们的数据、创建新文档、使用不同的标准和条件查询文档、执行简单和更复杂的聚合,以及以各种形式输出数据。你可能会说,真正的乐趣从这里开始!
Although we will be talking to MongoDB through our Python drivers (Motor and PyMongo), we believe that it is better to learn how to write queries directly. We will begin by querying the data that we have imported, as we believe that it is a more realistic scenario than just starting to make up artificial data, then we will go through the process of creating new data, inserting, updating, and so on. Let’s first define our two options for executing MongoDB commands as follows.
虽然我们将通过我们的Python驱动程序(Motor和PyMongo)与MongoDB对话,但我们认为最好是学习如何直接编写查询。我们将首先查询已导入的数据,因为我们相信这比开始创建人工数据更现实,然后我们将经历创建新数据、插入、更新等过程。首先定义执行MongoDB命令的两个选项,如下所示。
- Compass interface
- MongoDB shell
注意:这里书里的知识点是通过命令行实现的,我在本文中,则将相关的操作翻译为Python实现。
We will now set up both options for working and executing commands on our local database and the cloud database on Altas as well. Perform the following steps.
现在,我们将设置两个选项,以便在本地数据库和Altas上的云数据库上工作和执行命令。请执行以下步骤。
In as shell session (Command Prompt on Windows or Bash on Linux), run the following command.
在shell会话(Windows上是命令提示符,Linux上是Bash)中运行如下命令。
mongo
As earlier described when we were testing if the installation successed, we are going to be greeted with a minimal propt. Let’s see whether our carsDB database is still present and type the folowing commands.
正如前面所描述的,当我们测试安装是否成功时,我们将看到一个最小的提示。让我们看看我们的carsDB数据库是否仍然存在,并键入以下命令。
show dbs
Thisi command should list all of the available databases: admin, carsDB, config, and local.
这个命令应该列出所有可用的数据库:admin、carsDB、config和local。
可以使用以下Python代码替代:
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
print(client.list_database_names())
In order to use our database, let’s type the following code.
为了使用我们的数据库,让我们键入以下代码。
use carsDB
The console will respond with “switched to db carsDB”, and that means that now we can query and work on our database.
控制台将响应“切换到db carsDB”,这意味着现在我们可以查询和处理我们的数据库了。
To see the aailable collections inside the carsDB, try the following code:
要查看carsDB中可用的集合,请尝试以下代码:
show collections
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
print(db.list_collection_names())
You should be able to see our only collection, cars, the once in which we dumped our 7000+ documents from the downloaded CSV file. Now that we have our database and collection available, we can processed and explore some querying options.
您应该能够看到我们唯一的集合cars,其中我们从下载的CSV文件中转储了7000多个文档。现在我们有了可用的数据库和集合,我们可以处理和探索一些查询选项。
With Compass, we just have to start the program and click on the Connect button, without pasting the connection string. After that, you should be able to see all of the aforementioned databases in the left-hand menu and yu can just select carsDB and hen the cars collection, which resembles a folder inside the carsDB database.
使用Compass,我们只需要启动程序并点击连接按钮,而不需要粘贴连接字符串。之后,您应该能够在左侧菜单中看到上述所有数据库,您可以选择carsDB,然后选择cars集合,它类似于carsDB数据库中的一个文件夹。
Let’s start with some basic querying!
让我们从一些基本的查询开始!
Querying MongoDB
As we mentioned earlier, MongoDB relies on a query language that is based on methods, rathrer than SQL. This query language has different methods that operate on collections and take parameters as JavaScript objects that define the query. We believe it is much easier to see how querying works, so let’s try out some basic queries. We know we have over 7000 documents in our carsDB database, and these are real documents that, at a certain point in time a couple of years ago, defined real ads of a used cars sales website. We chose this dataset, or at least a part of it, because we believe that working with real data with some expected query results, and not abstract or artificial data, helps reinforce the acquied notions and makes understanding the underlying processes easier and more thorough.
正如我们前面提到的,MongoDB依赖于一种基于方法的查询语言,而不是SQL。这种查询语言有不同的方法来操作集合,并将参数作为定义查询的JavaScript对象。我们相信了解查询是如何工作的要容易得多,所以让我们尝试一些基本查询。我们知道在我们的carsDB数据库中有超过7000个文档,这些都是真实的文档,在几年前的某个时间点,定义了二手车销售网站的真实广告。我们选择了这个数据集,或者至少是其中的一部分,因为我们相信,使用具有一些预期查询结果的真实数据,而不是抽象或人工数据,有助于强化所获得的概念,并使对底层过程的理解更容易、更彻底。
The most frequent MongoDB query language commands, and the ones that we will be covering are the following.
- find(): A query for finding and selecting documents matching simple or complex criteria
- insertOne(): Inserts a new document into the collection
- insertMany(): Inserts an array of documents into the collection
- updateOne() and updateMany(): Update one or more documents according to some criteria
- deleteOne() and deleteMany(): Delete documents from the collection
最常见的MongoDB查询语言命令,以及我们将介绍的命令如下。
- find():查找和选择匹配简单或复杂条件的文档的查询
- insertOne():将新文档插入到集合中
- insertMany():将一个文档数组插入到集合中
- updateOne()和updateMany():按照一定的标准更新一个或多个文档
- deleteOne()和deleteMany():从集合中删除文档
As you can see, the MongoDB query methods closely match the HTTP verbs of a REST API!
正如您所看到的,MongoDB查询方法与REST API的HTTP谓词非常匹配!
We have over 7000 documents, so let’s take a look at them. To query for all the documents, type in the MongoDB shell the following command.
我们有7000多份文件,让我们来看看。要查询所有文档,请在MongoDB shell中输入以下命令。
db.cars.find()
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]results = cars.find()
print(results)
print(list(results))
The console will print the message Type “it” for more as the console print out only 20items at a time. This statement could be intrpreted as a classic SELECT * FROM TABLE in the SQL world. Let’s see how we can restrict our query and return only cars made in 2019 (it should be the last available year, as the dataset isn’t really fresh). In the command prompt, issue the following command.
控制台将打印消息Type“it”for more,因为控制台一次只打印20个项目。这条语句可以解释为SQL世界中的经典SELECT * FROM TABLE。让我们看看如何限制查询并只返回2019年生产的汽车(它应该是最后可用的年份,因为数据集并不新鲜)。在命令提示符中,发出以下命令。
db.cars.find({year:2019})
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]results = cars.find({"year": 2019})
print(results)
print(list(results))
The results should now contain only documents that satisfy the condition that the year key is equal to 2019. This is one of the keys or CSV fields that, when importing, we have set to the numeric type.
结果现在应该只包含满足年份键等于2019的条件的文档。这是在导入时设置为数字类型的关键字或CSV字段之一。
The JavaScript object that we used in the previous query is a filter, and it can have numerous key-value pairs with which we define our query method. MongoDB has many operators that enable us to query fields with more complex conditions than plain equality, and their updated documentation is available on the MongoDB site at https://docs.mongodb.com/manual/reference/operator/query/.
我们在前面的查询中使用的JavaScript对象是一个过滤器,它可以有许多键值对,我们用这些键值对定义查询方法。MongoDB有许多运算符,使我们能够查询比普通相等条件更复杂的字段,它们的更新文档可在MongoDB网站https://docs.mongodb.com/manual/reference/operator/query/上获得。
We invite you to visit the page and look around some of the operators as they can give you an idea of how you might be able to structure your queries. We will try combining a couple of them to get a feel for it.
我们邀请您访问该页面并查看一些操作符,因为它们可以让您了解如何构建查询。我们将尝试结合其中的几个来获得一个感觉。
Let’s say we want to find all Ford cars made in 2016 or later and priced at less than 7000 euros. The following query will do the job.
假设我们想找到所有2016年或之后生产的福特汽车,价格低于7000欧元。下面的查询将完成这项工作。
db.cars.find({year:{"$gt": 2015}, price:{"$lt": 7000}, brand:"Ford"}).pretty()
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]query = {"year": {"$gt": 2015}, "price": {"$lt": 7000}, "brand": "Ford"}
results = cars.find(query)
print(results)
print(list(results))
Notice that we used three filter conditions: $gt
:2015 means “greater than 2015”, we set the price to be less than 7000, and finally, we fixed the brand to be Ford
. The find() method implies an AND operation, so only documents satisfying all three conditions will be returned. At the end, we added the MongoDB shell function called pretty(), which formats the console output in a bit more readable way.
注意,我们使用了三个过滤条件:“$gt”:2015表示“大于2015”,我们将价格设置为小于7000,最后,我们将品牌固定为“Ford”。find()方法暗含AND操作,因此只返回满足所有三个条件的文档。最后,我们添加了名为pretty()的MongoDB shell函数,它以一种更易读的方式格式化控制台输出。
We have seen the find() method in action and we have seen a couple of examples where the find() operator takes a filter JavaScript objects in order to define aquery. Some of the most used query operators are $gt greater than, $lt less than, and $in providing a list of values, but as we can see from the MongoDB website, there are many more logical and, or , or nor, geospatial oerators for finding the nearest points on a map, and so on. It is time to explore other methods that allow us to perform queries and operations.
我们已经看到了find()方法的作用,我们还看到了几个例子,其中find()操作符接受一个JavaScript对象过滤器来定义查询。一些最常用的查询操作符是 g t 大于、 gt大于、 gt大于、lt小于和$in用于提供值列表,但正如我们从MongoDB网站上看到的,还有更多的逻辑和、或或或或或地理空间操作符,用于查找地图上最近的点,等等。现在是时候探索允许我们执行查询和操作的其他方法了。
findOne() is similar to find() and it takes an optional filter parameter but returns only the first document that satisfies the criteria.
findOne()类似于find(),它接受一个可选的过滤器参数,但只返回满足条件的第一个文档。
Before we dive into the process of creating or deleting and updating existing documents, we want to mention a very useful method called projection, which allows us to limit and set the fields that will be returned from the query results. The find() or findOne() method accepts an additional object that tells MongoDB which fields within the returned document are included or excluded.
在深入讨论创建或删除和更新现有文档的过程之前,我们想提一下一个非常有用的方法,称为投影,它允许我们限制和设置将从查询结果返回的字段。find()或findOne()方法接受一个附加对象,该对象告诉MongoDB返回的文档中包含或排除哪些字段。
Building projections is easy, it is just a JSON object in which the keys are the names of the fields, while the values are 0 if we want to exclude a field from the output, or 1 if we want to include it. The ObjectID is included by default, so if we want to remove it from the output, we have to set it to 0 explicitly. Let’s try it out just once, let’s say that we want the top 5 oldest Ford Fiestas with just the year of production and the kilometers on the meter, so we type the following command.
构建投影很容易,它只是一个JSON对象,其中键是字段的名称,如果我们想要从输出中排除字段,则值为0,如果我们想要包含它,则值为1。默认情况下包含ObjectID,所以如果我们想从输出中删除它,我们必须显式地将它设置为0。让我们只尝试一次,假设我们想要前5名最老的Ford Fiestas,仅包含生产年份和里程表上的公里数,因此我们输入以下命令。
db.cars.find({"brand":"Ford", "make":"Fiesta"}, {"year":1, "km":1, "_id":0}).sort({"year":1}).limit(5)
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]query = {"brand": "Ford", "make": "Fiesta"}
includes = {"year": 1, "km": 1, "_id": 0}
results = cars.find(query, includes).sort({"year": 1}).limit(5)
data = list(results)print(data)
print(len(data))
We sneaked in two things here, the sort and the limit parts, but we did it on purpose so we can have a glimpse of the MongoDB aggregations (albeit the simplest ones) and to see how intuitive the querying process can be. The projection part, however, is hidden in the second JSON object provided for the find() method. We simply stated that we want the year and the km variable, and then we suppressed _id since it will always be returned by default.
我们在这里隐藏了两件事,排序和限制部分,但我们故意这样做,以便我们可以对MongoDB聚合(尽管是最简单的聚合)有一个粗略的了解,并了解查询过程是多么直观。然而,投影部分隐藏在为find()方法提供的第二个JSON对象中。我们简单地声明我们想要年份和km变量,然后我们忽略了_id,因为它将在默认情况下始终返回。
Always reading and sorting the same documents can become a bit boring and quite useless in a real-life environment. In the next section, we will learn how to create and insert new documents into our database. Creating documents will be the entry point to any system that you will be building, so let’s dive in!
在现实生活中,总是阅读和排序相同的文档会变得有点无聊,而且毫无用处。在下一节中,我们将学习如何创建新文档并将其插入数据库。创建文档将是您将要构建的任何系统的切入点,因此让我们深入了解一下!
Creating new documents
The method for creating new documents in MongoDB is insertOne(). You can try insertng the followng ficitious car into our database.
在MongoDB中创建新文档的方法是insertOne()。您可以尝试将以下虚构的汽车插入到数据库中。
db.cars.insertMone({"brand":"Magic Car", "make": "Green Dragon", "year": 1200})
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]# 添加数据
data = {"brand": "Magic Car", "make": "Green Dragon", "year": 1200}
r = cars.insert_one(data)
print(r)
The first part means that MongoDB acknowledged the insertion operation, whereas the second property prints out the ObjectId, which is the primary key that MongoDB uses and assigns automatically if not provided manually.
第一部分表示MongoDB确认插入操作,而第二个属性打印出ObjectId,这是MongoDB使用的主键,如果没有手动提供,则会自动分配。
MongoDB, naturally, also supports inerting many documents at once with the insertMany() method. Instead of providing a single document, the method accepts an array of documents.
当然,MongoDB也支持使用insertMany()方法一次插入多个文档。该方法不提供单个文档,而是接受一个文档数组。
We could, for example, insert another couple of Magic Cars as follows.
例如,我们可以像下面这样插入另一对Magic Cars。
db.cars.insertMany([{"brand":"Magic Car", "make":"Yellow Dragon", "year": 1200},{"brand":"Magic Car", "make":"Red Dragon", "legs":4}])
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]# 添加数据
data = [{"brand": "Magic Car", "make": "Yellow Dragon", "year": 1200},{"brand": "Magic Car", "make": "Red Dragon", "legs": 4},
]
r = cars.insert_many(data)
print(r)
Here we inserted two new highly ficitious cars and the second one has a new property, legs, which does not exist in any other car, just to show off MongoDB’s schema flexibility. The shell acknowledges and prints out the ObjectIds of the new documents.
在这里,我们插入了两个新的高性能汽车,第二个汽车有一个新的属性legs,它不存在于任何其他汽车中,只是为了展示MongoDB的模式灵活性。shell确认并打印出新文档的目标。
Updating documents
Updating documents in MongoDB is possible through several different methods that are suited for different scenarios that might arise in your buisiness logic.
可以通过几种不同的方法来更新MongoDB中的文档,这些方法适用于业务逻辑中可能出现的不同场景。
The updateOne() method updates the first encountered document with the data provided in the fields. For example, let’s update the first Ford Fiesa that we find, add a Salesman field, and set it to Marko as follows.
updateOne()方法使用字段中提供的数据更新第一个遇到的文档。例如,让我们更新找到的第一辆Ford Fiesa,添加一个Salesman字段,并将其设置为Marko,如下所示。
db.cars.updateOne({"make":"Fiesta"},{"$set":{"Salesman":"Marko"}})
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]query = {"make": "Fiesta"}
updateData = {"$set": {"Salesman": "Marko"}}
r = cars.update_one(query, updateData)
print(r)
We can also update existing properties of the document as long as we use the $set operator. Let’s say that we want to reduce the prices of all Ford Fiestas in a linear way (not something you would want to do in real life, though) by 200. You could try it with the following command.
只要使用$set操作符,也可以更新文档的现有属性。假设我们希望将所有福特嘉年华(Ford Fiestas)的价格以线性方式降低200(但在现实生活中可能不会这么做)。您可以使用以下命令进行尝试。
db.cars.updateMany({"make":"Fiesta"},{"$inc":{"price": -200}})
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]query = {"make": "Fiesta"}
updateData = {"$inc": {"price": -200}}
r = cars.update_many(query, updateData)
print(r)
The preceding command updates many documents, namely all cars that statisfy the simple requirement of being a Fiesta (note that if another car producer, like Seat, decided to make a car named Fiesta, we would have to specify the brand as well) and makes use of the $inc operator (increment). Then, we pass the price field and the amount that we wish to increment the value. In our case, that would be minus 200 euros.
前面的命令更新了许多文档,即满足Fiesta这一简单要求的所有汽车(注意,如果另一个汽车制造商,如Seat,决定生产名为Fiesta的汽车,我们也必须指定品牌),并使用了$inc操作符(增量)。然后,我们传递price字段和我们希望增加的值。在我们的例子中,就是 -200 欧元。
Updating documents is an atomic operation, if two or more updates are issued at the same time ,the one that reaches the server first will be applied.
更新文档是一个原子操作,如果同时发出两个或多个更新,则首先到达服务器的更新将被应用。
MongoDB also provides a relaceOne operator that takes a filter, like our earlier methods, but expects also an entrire document that will take the place of the receding one. It is worth mentioning that there is also a very handy method, within updateOne(), that enables us to check whether the document to be updated exists and then update it, but in the case that no such document, exists, the method will create it for us. The syntax is the same as a standard updateOne() method, but with the {“upsert”:true} parameter.
MongoDB还提供了一个relaceOne操作符,它接受一个过滤器,就像我们前面的方法一样,但也期望一个完整的文档来代替后退的文档。值得一提的是,在updateOne()中还有一个非常方便的方法,它使我们能够检查要更新的文档是否存在,然后更新它,但是在不存在这样的文档的情况下,该方法将为我们创建它。语法与标准updateOne()方法相同,但使用了{“upsert”:true}参数。
Updating single documents should generally involve the use of the document’s ID.
更新单个文档通常应该涉及使用文档的ID。
Deleting documents
Deleting documents works in a similar way to the find methods, we provide a filter specifying the documents to be deleted and we can use the delete or the deleteMany method to execute the operation. Let’s delete all of our Magic Car automobiles under the pretext that they are not real, as follows.
删除文档的工作方式与find方法类似,我们提供了一个过滤器,指定要删除的文档,我们可以使用delete或deleteMany方法来执行该操作。让我们删除我们所有的神奇汽车的借口下,他们不是真实的汽车,如下。
db.cars.deleteMany({"brand":"Magic Car"})
import mongo6client = mongo6.MongoClient('mongodb://zhangdapeng:zhangdapeng520@192.168.77.129:27017/')
db = client["carsDB"]
cars = db["cars"]query = {"brand": "Magic Car"}
r = cars.delete_many(query)
print(r)
The shell will acknowledge this operation with a deletedCount variablke equal to 3, the number of deleted documents. The deleteOne method operates in a very similar way.
shell将使用一个deletedCount变量确认此操作,该变量等于3,即已删除文档的数量。deleteOne方法的操作方式非常相似。
Finally, we can always drop the entire cars collection with the following command.
最后,我们可以使用以下命令删除整个cars集合。
db.cars.drop()
Note: Make sure to import the data agin from the CSV file if you delete alll of the documents or drop the collection since there won’t be any data left to play with.
注意:如果删除所有文档或删除集合,请确保从CSV文件中再次导入数据,因为将没有任何数据可供使用。
Cursors
One important thing to note is that the find methods return a cursor and not the actual results. The actual iteration through the cursor will be executed in a particular and customized way through the use of a language driver to obtain the desired results. The cursor enables us to perform some standard database operations on the returned documents, such as limiting the number of results, ordering by one or more keys ascending or descending, skipping records, and so on.
需要注意的重要一点是,find方法返回的是游标,而不是实际结果。游标的实际迭代将通过使用语言驱动程序以特定和自定义的方式执行,以获得所需的结果。游标使我们能够对返回的文档执行一些标准的数据库操作,例如限制结果的数量、按一个或多个键升序或降序排序、跳过记录等等。
Since we are doing our MongoDB exploration using the shell(maybe you have been experimenting with Compass as well), we should point out that the shell automatically iterates over the cursor and displays the results. However, if we store the cursor in a variable, we can use some JavaScript methods on it and see that it exhibits, in fact, typical cursor behavior.
由于我们正在使用shell进行MongoDB探索(也许您也一直在使用Compass进行实验),因此我们应该指出shell会自动迭代游标并显示结果。然而,如果我们将游标存储在一个变量中,我们可以对它使用一些JavaScript方法,并看到它实际上显示了典型的游标行为。
Let’s create a cursor for the Ford Fiesta cars as follows:
让我们为福特嘉年华汽车创建一个光标,如下所示:
let fiesta_cars = db.cars.find({"make":"Fiesta"})
Now you can play around with the fiesta_cars variable and apply various methods such as next(), hasNext(), and similar cursor operations. The point is that the query is not sent to the server immediately when the shell encounters a find() call, but it waits until we start requesting data. This has several important implications, but it also enables us to apply an array of methods that return the cursor it self, thus enabling the chaining of methods.
现在可以使用fiesta_cars变量并应用各种方法,如next()、hasNext()和类似的游标操作。关键是,当shell遇到find()调用时,查询不会立即发送到服务器,而是等待我们开始请求数据。这有几个重要的含义,但它也使我们能够应用返回游标本身的方法数组,从而支持方法链。
Very similarly to jQuery or D3.js if you ever used them, chaining enables a nifty way of applying several operations on a cursor and returning a fine-tuned result set. Let’s see a simple example in action. We want the top 5 cheapest cars made in 2015, as follows.
与jQuery或D3.js非常相似(如果您曾经使用过它们),链接支持在游标上应用多个操作并返回经过微调的结果集的巧妙方式。让我们来看一个实际的简单示例。我们想要2015年最便宜的5款汽车,如下所示。
db.cars.find({year:2015},{brand:1,make:1,year:1,_id:0,price:1}).sort({price:1}).limit(5)