Pythonでmongodbを操作する～その３：update編～

#当記事の記載範囲
この記事ではPythonでmongodbに接続してから、update（SQLで言ってもupdate）の使い方について記載します。
内容としては以下になります。
1. update_one
2. update_many
3. replace_one
4. find_one_and_update
5. find_one_and_replace

mongodbの起動やpymongoのインストール方法については以下の記事をご覧いただければ幸いです。
https://qiita.com/bc_yuuuuuki/items/2b92598434f6cc320112

#準備データ
mongodbの準備データは以下のとおりです。

```
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
> show collections
employee
log
salary
> db.salary.find()
{ "_id" : ObjectId("5d4acf84de925ae437e2c123"), "name" : "山田", "salary" : 300000 }
{ "_id" : ObjectId("5d4acf84de925ae437e2c124"), "name" : "佐藤", "salary" : 400000 }
{ "_id" : ObjectId("5d4acf84de925ae437e2c125"), "name" : "田中", "salary" : 500000 }
{ "_id" : ObjectId("5d4b81a4de925ae437e2c126"), "name" : "山田", "salary" : 500000 }
```

#Pythonでupdateを使ってみる
まずはupdate_oneから使ってみます。
これから紹介する内容は基本的には同じ内容になっています。
第一引数のfileterに更新条件を設定し、第二引数のupdateやreplaceに更新する内容を設定します。
##update_oneの使い方

``` MongoUpdateSample.py
from pymongo import MongoClient

class MongoUpdateSample(object):

    def __init__(self, dbName, collectionName):
        self.client = MongoClient()
        self.db = self.client[dbName]
        self.collection = self.db.get_collection(collectionName)

    def find(self, projection=None,filter=None, sort=None):
        return self.collection.find(projection=projection,filter=filter,sort=sort)

    def update_one(self, filter, update):
        return self.collection.update_one(filter,update)

    def update_many(self, filter, update):
        return self.collection.update_many(filter,update)

    def replace_one(self, filter, replacement):
        return self.collection.replace_one(filter, replacement)

    def find_one_and_replace(self, filter, replacement):
        return self.collection.find_one_and_replace(filter, replacement)

mongo = MongoUpdateSample('test', 'salary')
find = mongo.find()
for doc in find:
    print(doc)

update = mongo.update_one({'name':'山田'},{'$set':{'salary':600000}})
print('更新件数：' + str(update.matched_count))
find = mongo.find()
for doc in find:
    print(doc)
```
やっている内容としては更新前のデータを表示し、nameフィールドが'山田'の給料を600000に更新する。
更新完了後に更新件数と更新後のデータを表示するというものです。

実行結果

```
--------------------更新前--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 300000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000.0}
更新件数：1
--------------------更新後--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 600000}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000.0}
```
はい。今回使ったのは**update_one**なので'name'フィールドが'山田'となっているデータ２件のうち、１件が更新されています。
正直、今のところあまり利用場面が思い浮かんでいません。。
気になるのは'salary'フィールドの値ですね。
今回用意したデータはmongodbのコンソールから登録を行いました。
その際、'salary'フィールドには整数で値を設定しているのですが、pymongoのfindでデータを取得するとfloatになっていることが分かります。
また、pymongoのupdate_oneで更新した整数の値は、findで取得するとint型になっています。
このあたり、キャストなど注意する必要がありそうです。
ちなみに更新後のデータをmongodbのコンソールで確認すると全て整数で表示されています。

```
> db.salary.find()
{ "_id" : ObjectId("5d4acf84de925ae437e2c123"), "name" : "山田", "salary" : 600000 }
{ "_id" : ObjectId("5d4acf84de925ae437e2c124"), "name" : "佐藤", "salary" : 400000 }
{ "_id" : ObjectId("5d4acf84de925ae437e2c125"), "name" : "田中", "salary" : 500000 }
{ "_id" : ObjectId("5d4b81a4de925ae437e2c126"), "name" : "山田", "salary" : 500000 }
```

##update_manyの使い方
update_manyの使い方はほとんどupdate_oneと同じです。
update_oneを呼び出している部分をupdate_manyに変更し、更新するパラメーターも変更しました。

``` （抜粋）MongoUpdateSample.py
mongo = MongoUpdateSample('test', 'salary')
find = mongo.find()
print('--------------------更新前--------------------')
for doc in find:
    print(doc)

update = mongo.update_many({'name':'山田'},{'$set':{'salary':7777777}})
print('更新件数：' + str(update.matched_count))
find = mongo.find()
print('--------------------更新後--------------------')
for doc in find:
    print(doc)
```

実行結果

```
--------------------更新前--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 600000}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000.0}
更新件数：2
--------------------更新後--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 7777777}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 7777777}
```
'name'フィールドが'山田'と一致する全てのデータの'salary'が更新されました。

##replace_oneの使い方
これはドキュメント（RDBで言うところのレコード）を丸々入れ替えるという内容になります。
mongodbにより一意に採番される「_id（主キー）」以外のフィールドを新しいフィールドに入れ替えることができます。
SQLには無い構文ですね。早速やってみましょう。

``` （抜粋）MongoUpdateSample.py
mongo = MongoUpdateSample('test', 'salary')
find = mongo.find()
print('--------------------更新前--------------------')
for doc in find:
    print(doc)

update = mongo.replace_one({'name':'山田'},{'fullname':'山田　太郎','rank':5,'profession':'Engineer','position':'Manager'})
print('更新件数：' + str(update.matched_count))
find = mongo.find()
print('--------------------更新後--------------------')
for doc in find:
    print(doc)
```
実行結果

```
--------------------更新前--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 7777777}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 7777777}
更新件数：1
--------------------更新後--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'fullname': '山田\u3000太郎', 'rank': 5, 'profession': 'Engineer', 'position': 'Manager'}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 7777777}
```
全角スペースが文字コードとして表示されたのが解せないですが、'_id'が'5d4acf84de925ae437e2c123'のデータが設定した値に置き換わっていることを確認できました。

##find_one_and_updateの使い方
これは名前のとおりfind_oneをしてupdateを行うというものです。
動きとしては関数の戻り値として、更新前のデータが取得できます。

``` （抜粋）MongoUpdateSample.py
mongo = MongoUpdateSample('test', 'salary')
find = mongo.find()
print('--------------------更新前--------------------')
for doc in find:
    print(doc)

update = mongo.find_one_and_update({'name':'山田'},{'$set':{'salary':800000}})
print(type(update))
print(update)
find = mongo.find()
print('--------------------更新後--------------------')
for doc in find:
    print(doc)
```
実行結果

```
--------------------更新前--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 300000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000}
<class 'dict'>
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 300000.0}
--------------------更新後--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 800000}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000}
```
動きとしては、find_oneで見つかったデータに対してupdateを行うようなので、更新件数は一件だけとなるようです。

##find_one_and_replaceの使い方
これも名前のとおり、find_oneをして、replaceを行うというものです。
find_one_and_updateと同じく、更新前のデータを取得できます。

``` （抜粋）MongoUpdateSample.py
mongo = MongoUpdateSample('test', 'salary')
find = mongo.find()
print('--------------------更新前--------------------')
for doc in find:
    print(doc)

update = mongo.find_one_and_replace({'name':'山田'},{'fullname':'山田　太郎','rank':5,'profession':'Engineer','position':'Manager'})
print(type(update))
print(update)
find = mongo.find()
print('--------------------更新後--------------------')
for doc in find:
    print(doc)
```
実行結果

```
--------------------更新前--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 800000}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000}
<class 'dict'>
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'name': '山田', 'salary': 800000}
--------------------更新後--------------------
{'_id': ObjectId('5d4acf84de925ae437e2c123'), 'fullname': '山田\u3000太郎', 'rank': 5, 'profession': 'Engineer', 'position': 'Manager'}
{'_id': ObjectId('5d4acf84de925ae437e2c124'), 'name': '佐藤', 'salary': 400000.0}
{'_id': ObjectId('5d4acf84de925ae437e2c125'), 'name': '田中', 'salary': 500000.0}
{'_id': ObjectId('5d4b81a4de925ae437e2c126'), 'name': '山田', 'salary': 500000}
```
replaceの前の情報を取得することが出来ました。

#感想
更新のやり方が普通のＳＱＬにはないreplaceなどのがあり、新鮮でした。
更新のやり方は完全にmongodbのコマンドとパラメーターの渡し方が同じで分かり易かったです。


#関連記事
- [Pythonでmongodbを操作する～その１～](https://qiita.com/bc_yuuuuuki/items/2b92598434f6cc320112)
- [Pythonでmongodbを操作する～その２：find編～](https://qiita.com/bc_yuuuuuki/items/23fa7ba63399e64cf321)