noSQL基礎與mongodb入門

今天這一篇粗淺的聊一聊非結構化數據存儲,以及R語言和Python與mongoDB之間的通訊。

寫這一篇是因為之前在寫web數據抓取的時候,涉及大量的json數據,當然我們可以直接將json轉換為R語言(dataframe/list)或者Python(dict/DataFrame)中的內置數據對象,但是保存原始數據往往也很重要,即便是list或者dict,如果不能轉化為關係型表格,通常也需要在本地保存成json格式的數據源。

那麼通過mongoDB這種專業的noSQL數據庫來保存非結構化數據,可以完成批量保存、批量讀取、條件查詢和更新,這樣可以集中維護,顯得更具有安全性、便利性、專業性。

mongo數據庫的數據對象是bson,這種數據結構相當於json標準的擴展,R語言中的list可以與json互轉,Python中的dict本身就與json高度兼容。

R語言

在R語言中,通常通過rmongodb包來進行非結構化數據存儲。(當然有替代的包,只是這個包資料相對較多一些!)

###下載:devtools::install_github("mongosoup/rmongodb")
library("rmongodb")

創建/斷開連接

mongo mongo.is.connected(mongo) #檢查是否連接成功
mongo.destroy(mongo) #斷開連接

關於如何在系統中啟動mongodb服務,網絡上有很多此類教程,照葫蘆畫瓢就好,如果你想使用一個類似MySQL的navicat那樣的可視化操作界面,可以考慮安裝Robo可視化界面,這樣基本就可以手動操作mongodb中的數據對象了。

mongodb中的數據對象,與MySQL中的數據對象略有不同,不過從層級上來看,仍然是分成數據庫 》集合(表) 》key-value.

一個數據庫中可以有很多個集合(相當於表),每一個集合中又包含很多的documents結構。每一個documents作為一條記錄,相當於SQL中的一行,而documents內是鍵值對結構,且允許包含嵌套結構。一個documents對象內嵌套的同一層級key-value對象,被稱為fileds,可以近似理解為SQL中的column。

noSQL基礎與mongodb入門

mongodb的數據對象叫做bson,是Binary JSON Serialization的縮寫簡稱,關於詳細的json和bson的概念及其內含關係,可以查閱相關資料,或者通過W3C網站了解。

接下來進入R語言與mongodb鏈接的操作講解。

以上已經建立了一個名為mongo的鏈接(mongo.is.connected結果可以用於測試連接是否成功!)。

###查看本地數據庫文件mongo.get.databases(mongo) #查看本地數據庫名稱mongo.get.database.collections(mongo, db = "pymongo_test") #查看pymongo_test數據庫內的各個集合名稱mongo.count(mongo, ns = "pymongo_test") #查看pymongo_test數據庫內的集合數量
mongo.rename(mongo, "pymongo_test.posts", "pymongo_test.post") #修改pymongo_test數據庫內posts表名稱

刪除操作

mongo.drop.database(mongo, db = "database") #移除數據庫及其內部所有集合mongo.drop(mongo, ns = "database.collection") #僅刪除數據庫內全部集合(collection)mongo.drop(mongo, ns = "rmongo_test.mydata1")#移除數據集合內的某一特定表mongo.remove(mongo, ns, criteria = mongo.bson.empty()) 
#移除集合內選定條件的記錄

noSQL基礎與mongodb入門

其中ns是命名空間參數,格式為“數據庫名稱.集合名稱”。

rmongodb內沒有專門創建數據庫或者在數據庫中創建集合的函數,想要創建的話僅需在插入數據時指定一個不存在的ns參數即可。

R語言中的非結構化數據對象是list,因為list結構與json或者bson差別比較大,在插入mongo之前需要使用特定函數進行list/json與bson之間的相互轉化。

涉及轉化的函數有兩個:

mongo.bson.from.JSON #將json對象轉換為mongodb中的bson對象。
mongo.bson.from.list #將list對象轉換為mongodb中的bson對象。

使用json格式數據插入mongo

#新建一個json對象json {"A":1,"B":2,"C":{"D":3,"E":4}} 
#注:使用jsonlite::toJSON函數將一個list轉為一個json字符串,這個字符串擁有一個名為json的類,
但是並未改變其內容,僅僅是添加了一個類,同時輸出的外觀優化了下。所以以上兩種list轉json的方法等價。
#將json對象轉換為mongodb可識別的bson對象:bson  A : 16 1
B : 16 2
C : 3
D : 16 3
E : 16 4
#轉化為basn後的數據結構內容未變,但是出現了樹狀層級結構。

插入mongo(注意這裡的rmongo_test.mydata是數據庫名+“.”+表名,而且數據庫名和表明都是不存在的,這樣會自動創建新數據庫及表)

mongo.get.databases(mongo)
[1] "pymongo_test"mongo.insert(mongo,ns="rmongo_test.mydata",bson)
[1] TRUE
mongo.get.databases(mongo)
[1] "pymongo_test" "rmongo_test"

使用list結構插入mongodb與使用json格式步驟差不多,不同的是要使用list轉bson的轉化函數。

list bson 
mongo.insert(mongo,"rmongo_test.mydata",bson) #使用之前的數據庫+表名會將本次插入的記錄添加到mydata已經存在的記錄後面mongo.insert(mongo,"rmongo_test.mydata1",bson) #換一個表名則會在rmongo_test數據庫中新建一個表mongo.drop(mongo, ns = "rmongo_test.mydata1")
#移除數據集合內的某一特定表(刪掉剛才新插的mydata1)

數據查詢

查詢其中一條記錄(第一條),使用mongo.find.one函數。

tmp  _id : 7 5a21346e5da941b6eb611cb7
A : 16 1
B : 16 2
C : 3
D : 16 3
E : 16 4

tmp對象是一個bson結構,需要轉化為list才能得到內置數據結構。

tmp $`_id`
{ $oid : "5a21346e5da941b6eb611cb7" }
$A

[1] 1$B
[1] 2$C
$C$D
[1] 3$C$E
[1] 4

查詢表中所有記錄要使用mongo.find.all函數。

find_all #find_all直接是將post內的bson對象轉化為一個list,很奇怪,
#為啥mongo.find.one輸出的是一個bson,需要使用函數轉為list,不是很理解設計的原因。

noSQL基礎與mongodb入門

mongo.find函數可以支持條件查詢:

#創建索引條件: 

buf mongo.bson.buffer.append(buf, name="gender", value="male")
query
構造查詢:
cursor [1] "mongo.cursor"cursor對象類似SQL中的一個遊標對象,不能直接查看內部結構,需要藉助迭代函數進行輸出
while (mongo.cursor.next(cursor)) #判斷是否還有剩餘迭代次數
print(mongo.cursor.value(cursor)) #打印當前迭代記錄
mongo.cursor.destroy(cursor) #關閉查詢遊標

_id : 7 5a21238873a36810a8c4896a
id : 2 20170101
name : 2 Jordan
age : 16 20
gender : 2 male
_id : 7 5a2123d173a36810a8c4896b
id : 2 20170101
name : 2 Jordan
age : 16 20
gender : 2 male
_id : 7 5a2123d173a36810a8c4896c
id : 2 20170202
name : 2 Mike
age : 16 21
gender : 2 male

所以也可以把cursor當成是一個迭代器,想要提取裡面的查詢數據,需要構造循環與迭代函數,自行提取,而mongo.find.one函數和mongo.find.all函數相當於兩個快捷函數,直接提取符合條件的記錄或者所有記錄。

rmongosb的mongo.find函數可以支持mongodb原生的複雜查詢,支持很多高級符號函數,這一點兒我暫未深入瞭解,留待以後再做探討。如果你想要詳細的瞭解mongodb的用法, 最好參考關於mongodb的專業操作書,rmongodb內的函數與mongodb的原生函數相比,還有很多地方不完善,無法支持,不過對於平時的數據存儲而言最夠了,用的最頻繁的就是插入、讀取操作了。

Python:

from pymongo import MongoClient,ASCENDING, DESCENDING
import pymongo,json

之前說到過,因為Python中的dict與json高度兼容(並不代表一模一樣),而bson結構又是基於json的擴展,所以在Python中可以直接將dict插入mongodb數據庫,而基本無需做類型轉換,這一點兒Python完勝R語言。

#連接MongDB:
client = MongoClient()
client = MongoClient(host='localhost',port= 27017)
client = MongoClient('mongodb://localhost:27017')

以上三種連接方法等價。

#連接數據庫:
db = client.pymongo_test
db = client['pymongo_test']

以上兩句等價,用於連接數據庫,與Python中訪問屬性的操作相同。

#指定集合(相當於SQL中的table)
collection = db.post
collection = db['post']

以上兩句等價,db的基礎上連接mongodb中的集合(相當於表)。

使用本地的json數據,創建一個帶插入的臨時dict結構:

mydata = json.load(open("D:/R/File/indy.json"))
mydata = mydata['indy movies']

mydata1 = mydata[1];mydata1
mydata2 = mydata[-2:];mydata2

為了防止數據混亂,現將之前在R語言中添加的表記錄刪除:

collection.remove({})
collection.insert_one(mydata1)
results = collection.find_one()
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}

當然也可以一次插入多條記錄,不過將記錄構造成一個列表即可。

type(mydata2)
list

collection.remove({})
collection.insert_many(mydata2)
for item in collection.find():
print(item)

{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}
{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}

查詢函數可以直接提供給for循環進行記錄的遍歷。

mangodb不允許插入重複記錄,還有一些保留字符要注意。(比如英文句點“.”)

查詢則提供了更為豐富的函數及可選參數。

#查詢一條記錄:
results = collection.find_one({'budget': 28170000})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}

條件查詢:

results = collection.find({'year': 1984})
for result in results:

print(result)

{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}

查詢條件支持符號函數以及正則表達式:

results = collection.find({'budget': {'$gt':30000000}})#budget大於30000000的記錄
for result in results:
print(result)

{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}

布爾條件查詢:

results = collection.find({'academy_award_ve': True})
for result in results:
print(result)

{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}

正則表達式查詢:

results = collection.find({'name': {'$regex': 'Doom$'}})
for result in results:
print(result)
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'name': 'Indiana Jones and the Temple of Doom', 'year': 1984, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'producers': ['Robert Watts'], 'budget': 28170000, 'academy_award_ve': True}

更新操作:

student = collection.find_one({'year':1984})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and the Temple of Doom', 'producers': ['Robert Watts'], 'year': 1984}
collection.update_one({"year": 1984}, {"$set": {"name": "Indiana Jones and Doom"}})
{'_id': ObjectId('5a2143c573a36810a8c4896f'), 'academy_award_ve': True, 'actors': {'Indiana Jones': 'Harrison Ford', 'Mola Ram': 'Amish Puri'}, 'budget': 28170000, 'name': 'Indiana Jones and Doom', 'producers': ['Robert Watts'], 'year': 1984}

刪除操作:

result = collection.delete_one({'name': 'Indiana Jones and Doom'})
data1 = collection.find()
for result in data1:
print(result)

{'_id': ObjectId('5a21451673a36810a8c48970'), 'name': 'Indiana Jones and the Last Crusade', 'year': 1989, 'actors': {'Indiana Jones': 'Harrison Ford', 'Walter Donovan': 'Julian Glover'}, 'producers': ['Robert Watts', 'George Lucas'], 'budget': 48000000, 'academy_award_ve': False}

刪除之後只剩一個記錄了。

Python支持的符號運算符還有很多!

符號含義示例

{'age': {'$lt': 20}} #$lt小於
{'age': {'$gt': 20}} #$gt大於
{'age': {'$lte': 20}} #$lte小於等於
{'age': {'$gte': 20}} #$gte大於等於
{'age': {'$ne': 20}} #$ne不等於
{'age': {'$in': [20, 23]}} #$in在範圍內
{'age': {'$nin': [20, 23]}} #$nin不在範圍內

正則表達式含義:

{'name': {'$regex': '^M.*'}} #$regex,name以M開頭
{'name': {'$exists': True}} #$exists,name屬性存在
{'age': {'$type': 'int'}} #$type,age的類型為int
{'age': {'$mod': [5,0]}} #$mod數字模操作,年齡模5餘0
{'$text': {'$search': 'Mike'}} #$text文本查詢,text類型的屬性中包含Mike字符串
{'$where': 'obj.fans_count == obj.follows_count'}#$where高級條件查詢,自身粉絲數等於關注數

這些運算符號以及正則表達式可以用在查詢、更新、刪除等所有操作上。

最後吐槽一句,R語言的rmongodb包的查詢函數實在是太麻煩了,很難用,Pymongo的函數設計就很友好。

以上便是R語言、Python與mongodb數據庫通訊的基礎操作,如果想要了解更為詳細的高階查詢操作,可以參考關於mongodb的專業技術書籍及資料。


分享到:


相關文章: