[Python3 / MongoDB] pymongoの処理呼出しを軽くまとめる

# 概要

タイトルの通りです。

最近MongoDBを使い始めたのだけれど、情報が色々なサイトに散らばっていて困っていたので、「自分が普段使いそうなものをまとめておく(`・ω・´)ｷﾘｯ」ということを目指した記事になっています。

# Versions

```bash
$ python -V
Python 3.7.1

$ pip list | grep pymongo
pymongo                  3.9.0
```

# 公式チュートリアル

https://api.mongodb.com/python/current/tutorial.html
私の記事なぞ見なくても、これがある....英語だが

# モジュールのimport

```python
from pymongo import MongoClient, DESCENDING, ASCENDING
```
ここに書いているimport文は実行済みのものとして、以下の記事を書いていきます。

# インスタンス作成

```python
# これからこれを使って行くよ
>>> m_client = MongoClient()
```

# 使えそうなメソッドを列挙してみる

どばー

```python
>>> dir(m_client)
['HOST', 'PORT', '_BaseObject__codec_options', '_BaseObject__read_concern', '_BaseObject__read_preference', '_BaseObject__write_concern', '_MongoClient__all_credentials', '_MongoClient__cursor_manager', '_MongoClient__default_database_name', '_MongoClient__index_cache', '_MongoClient__index_cache_lock', '_MongoClient__kill_cursors_queue', '_MongoClient__lock', '_MongoClient__options', '_MongoClient__start_session', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cache_credentials', '_cache_index', '_cached', '_close_cursor', '_close_cursor_now', '_constructor_args', '_database_default_options', '_encrypter', '_end_sessions', '_ensure_session', '_event_listeners', '_get_server_session', '_get_socket', '_get_topology', '_is_writable', '_kill_cursors', '_kill_cursors_executor', '_process_periodic_tasks', '_process_response', '_purge_credentials', '_purge_index', '_read_preference_for', '_repr_helper', '_reset_server', '_reset_server_and_request_check', '_retry_with_session', '_retryable_read', '_retryable_write', '_return_server_session', '_run_operation_with_response', '_select_server', '_send_cluster_time', '_server_property', '_slaveok_for_server', '_socket_for_reads', '_socket_for_writes', '_tmp_session', '_topology', '_topology_settings', '_write_concern_for', 'address', 'arbiters', 'close', 'close_cursor', 'codec_options', 'database_names', 'drop_database', 'event_listeners', 'fsync', 'get_database', 'get_default_database', 'is_locked', 'is_mongos', 'is_primary', 'kill_cursors', 'list_database_names', 'list_databases', 'local_threshold_ms', 'max_bson_size', 'max_idle_time_ms', 'max_message_size', 'max_pool_size', 'max_write_batch_size', 'min_pool_size', 'next', 'nodes', 'primary', 'read_concern', 'read_preference', 'retry_reads', 'retry_writes', 'secondaries', 'server_info', 'server_selection_timeout', 'set_cursor_manager', 'start_session', 'unlock', 'watch', 'write_concern']
```

# 1. 最低限の基本処理


## DB名を列挙

```python
>>> m_client.list_database_names()
['admin', 'candles', 'config', 'local'] # 私のmongodbにあったDB

# どっちでも同じ結果よ～
>>> m_client.database_names()
['admin', 'candles', 'config', 'local']
```


## DBを作成

なんと**不要**！
指定したDBが存在しなくても、そこにデータを投入するとDBも一緒に作成されます！
便利だけど、ちょっと怖い...？


## Collectionを作成

これも**不要**！
mongodbでは、DBの中にCollectionという枠があり、その中にレコードを挿入していくのですが、レコードinsertの時点で存在しないCollectionは自動で作成されます。

>※MongoDBに挿入されるのは、レコードではなくて本来は**ドキュメント**と呼ばれているが、1件ずつ挿入する単位としてよく使われるレコードという呼称を本記事では使用します

これも便利だけどちょっと怖いですね。
（Collection指定が間違っててもエラーが出ないかも）


## レコードの挿入(insert)

`インスタンス.db名.collection名.insert_one({'hoge': 'ほげ'})`
のような書き方になります。
※上で書いた通り、DB / Collectionともに未作成であっても、データを挿入すれば勝手に作成されます。

```python

>>> m_client.db_name.collection_name.insert_one({'hoge': 'ほげ1'})
<pymongo.results.InsertOneResult object at 0x7fb567329448>

# insertメソッドもあるけど、使うと怒られる
>>> m_client.db_name.collection_name.insert({'hoge': 'ほげ2'})
__main__:1: DeprecationWarning: insert is deprecated. Use insert_one or insert_many instead.
ObjectId('5dec7ab6f8f8434dbcab979a')
```
これで、 {'hoge': 'ほげ'} が2件insertされた。


## 選択(select *)

```python
>>> m_client.db_name.collection_name.find()
<pymongo.cursor.Cursor object at 0x7fb56730ddd8> # また君か...

# listでくくって配列化すると中身が見える
>>> list(m_client.db_name.collection_name.find())
[
    {'_id': ObjectId('5dec7ab6f8f8434dbcab979a'), 'hoge': 'ほげ1'},
    {'_id': ObjectId('5dec7ae3f8f8434dbcab979b'), 'hoge': 'ほげ2'}
]
```

さっき入れた ほげ が2件入っている。


## 選択(1件だけselect)

```python
>>> m_client.db_name.collection_name.find_one()
{'_id': ObjectId('5dec7ab6f8f8434dbcab979a'), 'hoge': 'ほげ1'}
```


## 条件を指定して選択(select * where ~)

```python
>>> list(m_client.db_name.collection_name.find({'hoge': 'ほげ2'}))
[
    {'_id': ObjectId('5dec7ab6f8f8434dbcab979a'), 'hoge': 'ほげ2'}
]
# 'hoge'列の値が 'ほげ2' になっているデータが他にもあれば全て取得される


# find_oneなら1件だけ選択
>>> m_client.db_name.collection_name.find_one({'hoge': 'ほげ'})
{'_id': ObjectId('5dec7ae3f8f8434dbcab979b'), 'hoge': 'ほげ'}
```

## 削除(delete)

```python
# 条件指定で最初に見つかったレコードが削除される
>>> m_client.db_name.collection_name.delete_one({'hoge': 'ほげ2'})
<pymongo.results.DeleteResult object at 0x7fb567344208>
```

# 2. もうちょっと頑張ってみる

## DBを列挙?

```python
# これだけだと中身が見えない....
>>> m_client.list_databases()
<pymongo.command_cursor.CommandCursor object at 0x7fb56730dcf8>

# ので、配列化する
>>> list(m_client.list_databases())
[
    {'name': 'admin', 'sizeOnDisk': 102400.0, 'empty': False}, 
    {'name': 'candles', 'sizeOnDisk': 1875968.0, 'empty': False},
    {'name': 'config', 'sizeOnDisk': 98304.0, 'empty': False},
    {'name': 'local', 'sizeOnDisk': 73728.0, 'empty': False}
]
```

何に使うのかは不明....(´・ω・`)
## '_id'を指定して挿入

mongodbは、レコードのinsert時に、各レコードに対して自動的にユニークな '_id' を割り振ってくれる。

が、'_id' を自分で指定したいときはこうする。

```python
>>> m_client.db_name.collection_name.insert({
    '_id': 'idだよ',
    'hoge': 'ほげ3'
})

# すると、こうなる
>>> list(m_client.your_db_name.your_collection_name.find())
[
    {'_id': ObjectId('5dec7ab6f8f8434dbcab979a'), 'hoge': 'ほげ1'},
    {'_id': ObjectId('5dec7ae3f8f8434dbcab979b'), 'hoge': 'ほげ'},
    {'_id': 'idだよ', 'hoge': 'ほげ3'}
]

# 日時とか入れるといいのかな？と思ってます
>>> import datetime
>>> m_client.db_name.collection_name.insert_one({'_id': datetime.datetime.now(), 'hoge': 'ほげ4'})
>>> list(m_client.db_name.collection_name.find())
[
    {'_id': ObjectId('5dec7ab6f8f8434dbcab979a'), 'hoge': 'ほげ1'},
    {'_id': ObjectId('5dec7ae3f8f8434dbcab979b'), 'hoge': 'ほげ'},
    {'_id': 'idだよ', 'hoge': 'ほげ3'},
    {'_id': datetime.datetime(2019, 12, 8, 14, 58, 31, 579000), 'hoge': 'ほげ4'}
]
```

## 複数件のレコードをinsert(bulk_insert)

`insert_many`というメソッドに、データを配列形式で渡せばよい

```python
# 実行したらこんな感じ（今回は実行してない）
m_client.db_name.collection_name.insert_many([
    {'hoge': '六角形'},
    {'hoge': 'ヘキサゴン'}
])
```

## Collection / DB の削除

```python
# Collection削除
>>> m_client.db_name.collection_name.drop()
>>> list(m_client.db_name.collection_name.find())
[]

# ↑ 1件もヒットしなくなった

# Collectionが一つもなくなったら、自動的にDBもなくなった！
>>> list(m_client.list_database_names())
['admin', 'candles', 'config', 'local']
```

## 選択結果をソート(order_by)

```python
# とりあえず、いっぱいレコードをinsert(5~6回実行)
>>> m_client.db_name.collection_name.insert_one({'_id': datetime.datetime.now(), 'hoge': 'ほげ'})

# 降順ソート(新しい日時が一番上になる)
>>> list(m_client.db_name.collection_name.find().sort('_id', DESCENDING))
[
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 56, 625000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 984000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 121000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 54, 696000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 9, 36, 233000), 'hoge': 'ほげ'}
]

# 昇順ソート(古い日時が一番上になる)
>>> list(m_client.db_name.collection_name.find().sort('_id', ASCENDING))
[
    {'_id': datetime.datetime(2019, 12, 8, 15, 9, 36, 233000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 54, 696000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 121000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 984000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 56, 625000), 'hoge': 'ほげ'}
]

# 実はこれでも行ける(こっちの方が好き)
>>> list(m_client.db_name.collection_name.find(sort=[('_id', DESCENDING)]))
```

## 特定列に対し範囲指定をして選択

範囲指定に使う識別子は大体これ

|      |以上| 超 |以下|未満|
|:----:|:--:|:--:|:--:|:--:|
|識別子|$gte| $gt|$lte| $lt|


※詳しくは　[ここ（MongoDB公式: comparison-expression-operators）](https://docs.mongodb.com/manual/reference/operator/aggregation/index.html#comparison-expression-operators)　みてくださいな...

```python
# 特定の値以上（その日時以降）
>>> list(m_client.db_name.collection_name.find(filter={
    '_id':{'$gte': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000)}
}))
[
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 984000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 56, 625000), 'hoge': 'ほげ'}
]

# 特定の値を超えた値（その日時を除いて、その日時以降）
>>> list(m_client.db_name.collection_name.find(filter={
    '_id':{'$gt': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000)}
}))
[
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 984000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 56, 625000), 'hoge': 'ほげ4'}
]

# 特定の値から特定の値まで
>>> list(m_client.db_name.collection_name.find(filter={
    '_id':{
        '$gte': datetime.datetime(2019, 12, 8, 15, 10, 55, 121000),
        '$lte':  datetime.datetime(2019, 12, 8, 15, 10, 55, 984000)
    }
}))
[
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 121000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 528000), 'hoge': 'ほげ'},
    {'_id': datetime.datetime(2019, 12, 8, 15, 10, 55, 984000), 'hoge': 'ほげ'}
]
```
`$gt` と `$lt` は逆の意味になります


## その他、条件指定を組み合わせて選択

ここが詳しいですね！
[pymongoを使った様々な検索条件（AND／OR／部分一致／範囲検索）](https://qiita.com/bc_yuuuuuki/items/cc5e3c97911da5e647ab)

（よって省略...）