這篇文章是閱讀Asabeneh的30 Days Of Python: Day 27 - Python with MongoDB後的學習筆記與心得。
這個章節會使用繼續使用 Day 27 的 Flask 框架並搭配一個很常見的 noSQL 資料庫選擇—MongoDB;noSQL 和 SQL 就我淺白的理解,就是一個是卡片式,另一個是表格式的管理資料。
申請帳號及起始環境的部分可以參考原文章,但順序有一點變動。或是比較近期的文章;因為有一點差異,像是現在是先新增使用者,然後才選擇雲端服務供應商及區域,這部份我選擇 AWS,區域選東京。
完成後會拿到一串連結用的字串:
mongodb+srv://username:<password>@30daysofpython-twxkr.mongodb.net/test?retryWrites=true&w=majority
要把 <password>
的部份換成剛剛新增使用者時設置的密碼。
[!DANGER] 記得不要放到 Git 裡
因為密碼就在這個 link 裡,你不應該把這個資訊 commit 到 git 或甚至上傳到公開的 GitHub repo。
需要安裝 pymongo
以及 dnspython
這兩個套件
[!INFO] 參考 MongoDB 這個頁面說明
PyMongo 是同步處理,異步處理可以使用 Motor
pip install pymongo dnspython
類似於 Day 27 的設置,起一個伺服器,但基本上今天不會用到網頁,看終端機 (terminal) 那邊的訊息就能確認結果:
from flask import Flask
import os
import pymongo
MONGODB_URI = "mongodb+srv://username:your_password_goes_here@30daysofpython-twxkr.mongodb.net/test?retryWrites=true&w=majority"
client = pymongo.MongoClient(MONGODB_URI)
print(client.list_database_names())
app = Flask(__name__)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(debug=True, host="0.0.0.0", port=port)
這邊應該能看到終端有印出 ['admin', 'local']
代表有連到 MongoDB。
這邊開始可以沿用上一個階段的程式碼,只要修改部份的命令就好,以下面例子來說:
from flask import Flask
import os
import pymongo
MONGODB_URI = "mongodb+srv://username:your_password_goes_here@30daysofpython-twxkr.mongodb.net/test?retryWrites=true&w=majority"
client = pymongo.MongoClient(MONGODB_URI)
# 連線到現有的資料庫,或創造一個
db = client["thirty_days_of_python"]
# 新增一個 collection - studuents 並加入一個項目
db.students.insert_one({"name": "Asabeneh", "country": "Finland", "city": "Helsinki", "age": 250})
print(client.list_database_names())
app = Flask(__name__)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(debug=True, host="0.0.0.0", port=port)
db
這個變數在後面的例子會一直用到。students
這個 db
的屬性就是 collection,可以在我們創建的 MongoDB 頁面,進入 30DaysOfPython 的專案,點選 collection 頁籤看到每次操作存入的資料狀況。client.list_database_names()
的回傳結果看到新增的 database:['thirty_days_of_python', 'admin', 'local']
。現在已經有了 students 這個 collection,想要新增新的項目進去,就如同前面創造一樣,使用 insert_one(item)
方法:
記得要先把前面的
insert_one
刪掉,不然會重複新增。
students = [
{'name':'David','country':'UK','city':'London','age':34},
{'name':'John','country':'Sweden','city':'Stockholm','age':28},
{'name':'Sami','country':'Finland','city':'Helsinki','age':25},
]
for student in students:
db.students.insert_one(student)
可以使用回傳第一筆資料的 find_one()
或是 回傳全部資料的 find()
:
first_student = db.students.find_one()
print(first_student)
students = db.students.find()
for student in students:
print(student)
印出結果:
{'_id': ObjectId('634651ae7d83d79be86e9603'), 'name': 'Asabeneh', 'country': 'Finland',
'city': 'Helsinki', 'age': 250}
--- 方便閱讀的分隔線(非實際印出) ---
{'_id': ObjectId('634651ae7d83d79be86e9603'), 'name': 'Asabeneh', 'country': 'Finland',
'city': 'Helsinki', 'age': 250}
{'_id': ObjectId('63465741861a2ea6862cb7ef'), 'name': 'David', 'country': 'UK', 'city':
'London', 'age': 34}
{'_id': ObjectId('63465741861a2ea6862cb7f0'), 'name': 'John', 'country': 'Sweden', 'city': 'Stockholm', 'age': 28}
{'_id': ObjectId('63465741861a2ea6862cb7f1'), 'name': 'Sami', 'country': 'Finland', 'city': 'Helsinki', 'age': 25}
另外 find(WHERE, SELECT)
可以接受兩個參數,第一個是要符合的條件,第二個是要回傳的欄位,就像 SQL 中的 WHERE 和 SELECT 的作用;可以兩個參數都給,但這邊為了方便演示各自都只給一個參數:
# 0是不要,1是要,這邊只要 name 和 country 的欄位資料
students = db.students.find({}, {"_id": 0, "name": 1, "country": 1})
for student in students:
print(student)
# 查找 name = David 的項目
names = db.students.find({"name": "David"})
for name in names:
print(name)
ages = db.students.find({"age": {"$gt": 30}}, {"_id": 0, "name": 1, "age": 1})
for age in ages:
print(age)
印出結果;分隔線是用來加強可讀性的,非實際印出:
--- students ---
{'name': 'Asabeneh', 'country': 'Finland'}
{'name': 'David', 'country': 'UK'}
{'name': 'John', 'country': 'Sweden'}
{'name': 'Sami', 'country': 'Finland'}
--- names ---
{'_id': ObjectId('63465741861a2ea6862cb7ef'), 'name': 'David', 'country': 'UK', 'city':
'London', 'age': 34}
--- ages ---
{'name': 'Asabeneh', 'age': 250}
{'name': 'David', 'age': 34}
使用 limit()
方法,接在 find()
方法後:
first_two_students = db.students.find({}, {"_id": 0, "name": 1, "country": 1}).limit(2)
for student in first_two_students:
print(student)
""" 印出結果
{'name': 'Asabeneh', 'country': 'Finland'}
{'name': 'David', 'country': 'UK'}
"""
使用 sort(key_or_list, direction)
方法,接在 find()
方法後:
key
是排序要依據的欄位名稱。direction
如果是 1 會是升冪排序,-1 則是降冪排序。query = {
"_id": 0,
"name": 1,
"country": 1,
"age": 1
}
students = db.students.find({}, query).sort("age", 1)
for student in students:
print(student)
""" 印出結果
{'name': 'Sami', 'country': 'Finland', 'age': 25}
{'name': 'John', 'country': 'Sweden', 'age': 28}
{'name': 'David', 'country': 'UK', 'age': 34}
{'name': 'Asabeneh', 'country': 'Finland', 'age': 250}
"""
使用 update_one(filter, new_values)
方法來更新目前 collection 中有的條目:
這個方法會搭配
$set
這個 operator。
query = {"age": 250}
new_value = {"$set":{"age": 38}}
db.students.update_one(query, new_value)
for student in db.students.find():
print(student)
""" 印出結果
{'_id': ObjectId('634651ae7d83d79be86e9603'), 'name': 'Asabeneh', 'country': 'Finland',
'city': 'Helsinki', 'age': 38}
{'_id': ObjectId('63465741861a2ea6862cb7ef'), 'name': 'David', 'country': 'UK', 'city':
'London', 'age': 34}
{'_id': ObjectId('63465741861a2ea6862cb7f0'), 'name': 'John', 'country': 'Sweden', 'city': 'Stockholm', 'age': 28}
{'_id': ObjectId('63465741861a2ea6862cb7f1'), 'name': 'Sami', 'country': 'Finland', 'city': 'Helsinki', 'age': 25}
"""
如果要更新多筆資料的話,可以使用 update_many(filter, update)
方法。
使用 delete_one(filter)
刪除 collection 中符合條件的第一個項目:
query = {"name": "John"}
db.students.delete_one(query)
for student in db.students.find():
print(student)
"""印出結果
{'_id': ObjectId('634651ae7d83d79be86e9603'), 'name': 'Asabeneh', 'country': 'Finland',
'city': 'Helsinki', 'age': 38}
{'_id': ObjectId('63465741861a2ea6862cb7ef'), 'name': 'David', 'country': 'UK', 'city':
'London', 'age': 34}
{'_id': ObjectId('63465741861a2ea6862cb7f1'), 'name': 'Sami', 'country': 'Finland', 'city': 'Helsinki', 'age': 25}
"""
如果要刪除多筆資料的話,可以使用 delete_many(filter)
方法。如果給 filter
參數是空物件 {}
的話,會刪除 collection 中所有資料。
這是一個危險的操作
使用 drop()
方法,會把該資料庫刪除,以這篇文章的例子來看,thirty_days_of_python 連同裡面的 collection students
都會被刪除:
db.students.drop()