2016-03-07 81 views
1

我目前正在嘗試編寫一個拼圖網站的MongoDB後端。我對pymongo相當陌生,我一直在努力尋找一種方法來檢查唯一的密鑰標識符,並在子文檔退出時更新它。我的佈局是這樣的:檢查_id是否存在使用和更新子文檔Pymongo

{ 
_id : Jack 
"username": Jack 
"puzzles": [ 
    { 
     "name": puzName, 
     "rank": rank, 
     "date": puzDate, 
     "Global Score": score, 
     "Points": points 
    } 
], 
"attempts": 1 
} 

如果傑克已經存在,我想它這樣做:

{ 
_id : Jack 
"username": Jack 
"puzzles": [ 
    { 
     "name": puzName, 
     "rank": rank, 
     "date": puzDate, 
     "Global Score": score, 
     "Points": points 
    } 
    { 
     "name": puzName2, 
     "rank": rank, 
     "date": puzDate, 
     "Global Score": score, 
     "Points": points 
    } 
], 
"attempts": 2 
} 

要填充字段,我是從現有的HTML和使用美麗的湯服用領域。

cells = row('td') 
rank = cells[0].string 
name = cells[1].find_all('a')[1].find(text=True).strip() 
score = row('td')[3].string 
points = row('td')[4].string 

puz_dict = {} 
puz_dict['_id'] = name.encode('ascii','ignore') 
puz_dict['username'] = name.encode('ascii','ignore') 
puz_dict['puzzles'] = {'Puzzle Name': puzName, 'Rank': int(str(rank)), "Date": puzDate,'Global Score' : locale.atoi(str(score)), 'Points' : int(str(points)) } 
puz_dict['attempts'] = 1 

connection = MongoClient('localhost') 
coll = connection['Puzzles']['Users'] 
if col.find({'_id' : puz_dict['_id']}).count() > 0: 
    Print "Updating User" 
    update stuff 
else:  
    coll.insert(puz_dict) 

正如您所見,我使用用戶名作爲唯一標識文檔的方式。到現在爲止還挺好。檢查數據庫,用戶信息正確填充。

現在我想檢查用戶是否已經存在,如果他們這樣做,更新「拼圖」字段以包含該拼圖並將更新增加1.我認爲這可以檢查存在,但它似乎並沒有工作,而是直接插入:

if col.find({'_id' : puz_dict['_id']}).count() > 0: 
    Print "Updating User" 
    update stuff 

爲什麼它沒有正確檢查?我如何更新子文檔?

+0

而如果「用戶」不存在?如何使用['update_one()'](https://api.mongodb.org/python/current/api/pymongo/collection.html#pymongo.collection.Collection.update_one)方法? – styvane

+0

所以如果用戶(_id)不存在於集合中,那麼計數應該是0.如果它是0,那麼else中的insert()將被執行。這將在集合中創建一個新的_id和用戶。我沒有包含我的更新代碼,因爲我沒有測試過它。我想讓find()正常工作首先更重要 – jaybeatle

+0

您的find()查詢是否返回任何文檔?你也有一個錯字。你正在使用'col.find()'而不是'coll.find()',也許這是罪魁禍首。但坦率地說,你不需要使用'coll.find()。count()''你可以使用'update_one()'方法並將'upsert'選項設置爲'True',然後檢查'upserted_id'的值和'modified_count'並相應地打印一條消息。 – styvane

回答

2

好吧,因爲你看起來對數據庫一般都很陌生,所以它可能會引發你正確的事情是「找到」事情,然後「更新」和「保存」,而只是發送一個"update"請求:

coll = connection['Puzzles']['Users'] 

# after each assignment 

coll.update_one(
    { "_id": puz_dict["_id"] }, 
    { 
     "$setOnInsert": { "username": puz_dict["username"] }, 
     "$push": { "puzzles": puz_dict["puzzles"] }, 
     "$inc": { "attempts": puz_dict["attempts"] } 
    }, 
    upsert = True 
) 

因此,這些「更新」工作通過查找該_id值相匹配的文檔,然後考慮採取以下行動:

  • $push包含將被添加到一個數組字段的內容。因此,任何新內容都將被附加到名爲"puzzles"的文檔中的數組中。

  • $inc將查看文檔中的當前值"attempts",然後通過提供的任何值作爲參數「增加」該值。

  • $setOnInsert是特殊的,而不是對每個匹配的文檔進行更改,而只是在出現upsert時進行提供的修改。

  • upsert當然終凝,這意味着其中_id值不匹配,那麼一個新的文檔將被代替創建具有該被用來尋找文檔該_id值,然後任何內容的那在$setOnInsert中被提及。

當然的每一個匹配的文件或創建的文檔會受到其他$push$inc操作,所以這些將始終適用,無論是對現有的內容或通過增加以匹配已找到的內容文件。

在最好的情況下,循環數據源時,最好是在"bulk"犯這種「寫」到數據庫中,而不是僅僅一次發送的每個操作之一:

# import the UpdateOne bulk helper 
from pymongo import UpdateOne 

# Outside loop of writing sourcing data 
operations = [] 

# Inside loop of sourcing data, add to the queue 

operations.append(
    UpdateOne(
     { "_id": puz_dict["_id"] }, 
     { 
      "$setOnInsert": { "username": puz_dict["username"] }, 
      "$push": { "puzzles": puz_dict["puzzles"] }, 
      "$inc": { "attempts": puz_dict["attempts"] } 
     }, 
     upsert = True 
    )  
) 

# Only write to server 1 in 1000 and clear the queue 
if (len(operations) % 1000 == 0): 
    coll.bulk_write(operations) 
    operations = [] 

# Finish the loop 

# Then only write again if there will still queued operations 
# remaining on loop completion 

if (len(operations) > 0): 
    coll.bulk_write(operations) 

這基本上是如何你可以通過爲每一行細節添加操作作爲輸入,然後一次寫入多個操作(理想情況下可能與驅動程序一致爲1000或更少),而不是單獨寫入。

但無論如何,沒有必要「查詢」數據作爲單獨的請求,因爲這是什麼「更新」特別是「upserts」是要處理的。原子操作允許「就地」修改數據,因此在更改之前不需要閱讀文檔內容。


還要注意的是「關係」,如MongoClient得到的只能每天在你的應用程序生命週期發生一次。無論您的應用程序實際在做什麼,該連接都應該可用,並在該應用程序的整個生命週期中持續存在,直至完成或終止。

+0

感謝您的詳細回覆!我從其他人那裏接受了這個項目,坦率地說,我有點無知。我閱讀了大量的文檔和示例,但是當有人直接解決問題時,它總是更容易! – jaybeatle