What's "MongoDB" ?

よく使い方を忘れるので、必要な時にサッと読み返せるように超完結にまとめておきます。

MongoDBを学習する目的は、Node.jsの入門用アプリを作成しようとしたところ「Node.js」「Express」「MongoDB」という　組み合わせを三種の神器であるかのように頻繁に目にし、基礎を習得しておこうと考えたため。

>勉強中の関連記事まとめ
>[自分用の勉強記事をまとめた目次　~擬似知識体系~](https://qiita.com/oekaki-hoho-ron/items/3194f560efc461ec03b0)

『MongoDBの薄い本』という素晴らしい教材があったので、これをメインに学習を進めていきます。この記事は、この本の内容を初心者なりに再解釈してまとめた内容となります。

>参考文献
>[MongoDBの薄い本(The Little MongoDB Book)](https://www.cuspy.org/diary/2012-04-17/)
>[MongoDB ダウンロード](https://www.mongodb.com/download-center/community)

#MongoDBとは

###データベース

データベースとは、コンピュータ上に大量の情報を蓄積し、効率よく検索できるようにしたソフトウェアのこと。または、コンピュータ上で扱う情報の集合のこと。

###リレーショナルデータベース（RDB）

RDBとは、データベースの実現方式の1つで、複数の表とそれらの関係を定義することで情報を管理するもの。SQLによるデータの操作がやりやすい。

リレーショナルデータベースとは、事前定義された、関連があるデータ項目の集合体。つまりは表の形式で管理されるデータ保管場所ということ。

#インストールの仕方が変わったらしい

mongoDBをインストールする方法としてbrewを使う解説記事が検索で多く見つかるが、そのままやるとエラーになるように変更されているらしい。公式の解説ではbrewをつかってtapするみたいな話になってたのでメモ。

公式チュートリアルのOSX用の手順を解読しておこうとおもう。原文が英語なことより、そもそも何のためのコマンドなのかを把握しておきたい。

>参考文献
>[Install MongoDB](https://docs.mongodb.com/guides/server/install/)
>[Install MongoDB Community Edition on macOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)

###概要

[Homebrew](https://brew.sh/?jmp=mongodb-docs)を使ってMongoDB 4.2 Community EditionをmacOSにインストールするチュートリアル。

MongoDB 4.2+はIntel x84-64上のOSバージョン10.12+のみをサポートしている。詳細は[サポートされているプラットフォーム](https://docs.mongodb.com/manual/installation/#mongodb-supported-platforms)を参照。

###インストール

前提条件として、Homebrewパッケージと、[公式のMongoDBのTapパッケージ](https://github.com/mongodb/homebrew-brew)をインストールしている場合、「MongoDBのインストール」の項目までスキップできる。そうでないなら以下の3つを行う

- XCodeをインストール
- [Homebrew](https://brew.sh/#install)をインストール
- [MongoDB Homebrew Tap](https://github.com/mongodb/homebrew-brew)をタップ

```zsh
$ brew tap mongodb/brew
```

###MongoDBのインストール

```zsh
$ brew install mongodb-community@4.2
```

インストールすると[バイナリ](https://docs.mongodb.com/manual/reference/program/)に加えて以下のファイルが作成される。

- 設定ファイル　(/usr/local/etc/mongod.conf)
- ログファイルパス　(/usr/local/var/log/mongodb)
- データファイルパス　(/usr/local/var/mongodb)

###MongoDBの実行

MongoDBをmacOSサービスとして実行するにはbrewを使うか、MongoDBをバックグラウンドプロセスとして手動で実行する。おすすめはbrewを使う方。実行すると正しいシステム[ulimit](https://docs.mongodb.com/manual/reference/ulimit/#ulimit-settings)値（？）が自動で設定される。

mongodプロセスをmacOSサービスとして実行する方法。

```zsh
$ brew services start mongodb-community@4.2
```

mongodプロセスをバックグラウンドプロセスとして手動で実行する方法。

```zsh
$ mongod --config /usr/local/etc/mongod.conf --fork
```

いずれの方法でもインストール中に作成された`/usr/local/etc/mongod.conf`が使用される。このファイルに独自のMongoDBの[構成オプション](https://docs.mongodb.com/manual/reference/configuration-options/)を追加することもできる。

###MongoDBが実行されていることを確認する

実行中のmongodプロセスを検索する。

```zsh
$ ps aux | grep -v grep | grep mongod
```

###MongoDBの接続と使用

MongoDBの使用を開始するには、実行中のインスタンスにシェルを接続する。

```zsh
$ mongo
```

CRUD（作成・読み取り・更新・削除）操作の詳細については以下を参照。

- [文書を挿入](https://docs.mongodb.com/manual/tutorial/insert-documents/)
- [クエリドキュメント](https://docs.mongodb.com/manual/tutorial/query-documents/)
- [ドキュメントを更新する](https://docs.mongodb.com/manual/tutorial/update-documents/)
- [ドキュメントを削除する](https://docs.mongodb.com/manual/tutorial/remove-documents/)

###備考

以前に古いバージョンをインストールしたことがある場合、以下のようなChecksumMismatchErrorが発生する可能性がある。

```zsh
Error: An exception occurred within a child process:

  ChecksumMismatchError: SHA256 mismatch

Expected: c7214ee7bda3cf9566e8776a8978706d9827c1b09017e17b66a5a4e0c0731e1f

  Actual: 6aa2e0c348e8abeec7931dced1f85d4bb161ef209c6af317fe530ea11bbac8f0

 Archive: /Users/kay/Library/Caches/Homebrew/downloads/a6696157a9852f392ec6323b4bb697b86312f0c345d390111bd51bb1cbd7e219--mongodb-macos-x86_64-4.2.0.tgz

To retry an incomplete download, remove the file above.
```

修正するには、

1. ダウンロードしたtgzアーカイブを削除する。
2. 数式を再度タップする。

```zsh
$ brew untap mongodb/brew && brew tap mongodb/brew
```
3 . インストールを再試行する。

```zsh
$ brew install mongodb-community@4.2
```

###ローカルホストのバインディング

MongoDB 3.6以降、バイナリ、[mongod](https://docs.mongodb.com/manual/reference/program/mongod/#bin.mongod)および[mongos](https://docs.mongodb.com/manual/reference/program/mongos/#bin.mongos)はデフォルトでlocalhostでバインドする。

ローカルホストのみにバインドされている場合、これらのバイナリは同じマシンで実行されているクライアントからの接続のみを受け入れることができ、リモートクライアントは接続できない。他のIPアドレスをオーバーライドおよびバインドする方法の詳細については「[ローカルホストバインディングの互換性の変更](https://docs.mongodb.com/manual/release-notes/3.6-compatibility/#bind-ip-compatibility)」を参照。

#コマンド

###バージョン確認　--version

```
> mongo --version
MongoDB shell version v4.2.1
git version: edf6d45851c0b9ee15548f0f847df141764a317e
allocator: system
modules: none
build environment:
    distarch: x86_64
    target_arch: x86_64
```

###MongoDB起動　mongo

```
> mongo
MongoDB shell version v4.2.1
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("488a6fc4-e92c-47b4-b966-df48d86a18d8") }
MongoDB server version: 4.2.1
Server has startup warnings: 
2019-12-10T20:09:34.654+0900 I  CONTROL  [initandlisten] 
2019-12-10T20:09:34.654+0900 I  CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2019-12-10T20:09:34.654+0900 I  CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2019-12-10T20:09:34.654+0900 I  CONTROL  [initandlisten] 
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
```

###ヘルプ　help

```
> help
	db.help()                    help on db methods
	db.mycoll.help()             help on collection methods
	sh.help()                    sharding helpers
	rs.help()                    replica set helpers
	help admin                   administrative help
	help connect                 connecting to a db help
	help keys                    key shortcuts
	help misc                    misc things to know
	help mr                      mapreduce

	show dbs                     show database names
	show collections             show collections in current database
	show users                   show users in current database
	show profile                 show most recent system.profile entries with time >= 1ms
	show logs                    show the accessible logger names
	show log [name]              prints out the last segment of log in memory, 'global' is default
	use <db_name>                set current database
	db.foo.find()                list objects in collection foo
	db.foo.find( { a : 1 } )     list objects in foo where a == 1
	it                           result of the last line evaluated; use to further iterate
	DBQuery.shellBatchSize = x   set default number of items to display on shell
	exit                         quit the mongo shell
```

---

###データベース一覧を見る　show dbs

```
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```

###データベース作成（切替）　use

この場合mydbという名前。しかし作成した時点ではまだ有効なデータベースとして数えられていない。`show dbs`をしても一覧されていないのがわかる。

```
> use mydb
switched to db mydb

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```

###データベースの削除　db.dropDatabase

必ず削除したいデータベースに切り替わっていることを`db.stats()`で確認すること。

```
> db.dropDatabase()
{ "dropped" : "mydb", "ok" : 1 }

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```
---

###データベースにコレクションを追加　db.createCollection()

この場合usersという名前。データベースにコレクションを追加すると`show dbs`のリストに追加される。

```
> db.createCollection("users")`
{ "ok" : 1 }

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
mydb    0.000GB
```

###データベースの中身（コレクション）を見る　show collections

```
> show collections
users
```

###コレクションの名前を変更　db.colName.renameCollection()

この場合はusersという名前をcustomersに変更している。

```
> db.users.renameCollection("customers")
{ "ok" : 1 }

> show collections
customers
```

###コレクションを削除　db.colName.drop()

`drop`すればコレクションのcustomersを削除する。削除されたので`show collections`しても表示されなくなった。

```
> db.customers.drop()
true

> show collections

```

もしくは、`remove`すれば消せるが、このときremoveにコレクション名を指定せず空のオブジェクトを渡せば全件削除となる。

```
> db.users.remove({})
WriteResult({ "nRemoved" : 11 })

> show collections
users

> db.users.find()
> 
```

###コレクションの挿入　db.colName.insert()

まず`db.コレクション名.insert(`でEnterを押せば続けてオブジェクトの中身を入力するようになる。そこで`{name:"山田", score:30}`をusersという名前のコレクションとして挿入するための`db.users.insert({name:"山田", score:30})`という記述になる。

```
> db.users.insert(
... {
... name:"山田",
... score:30
... }
... )
WriteResult({ "nInserted" : 1 })

> show collections
users
```

mongoDBはスキーマレスのNoSQLなので、違う構造のデータも挿入できる。3つ目のtagsのような複数あるコレクションは配列として渡している。

```
> db.users.insert(
... {
... name:"",
... score:58,
... tags:["web", "mobile"]
... })
WriteResult({ "nInserted" : 1 })
```

mongoDBではJavaScriptが使えるのでループ文を使ってコレクションを挿入することもできる。例として、for文で10回ループしてランダムなscoreの値を生成する場合は下記のように記述する。

```
> for(let i=0; i<10; i++){
... db.users.insert({
... score:Math.random()
... })
... }
WriteResult({ "nInserted" : 1 })
```

###コレクションの数を見る　db.colName.count()

上記のコードでコレクションを生成した後でその中身の数を表示すると、元あったもの + 10回ループで生成したもの = 11個のコレクションが入っている。

```
> db.users.count()
11
```

###全てのコレクションをリストアップする　db.colName.find()

usersコレクションの中身をリストアップしたもの。これは上記の10回ループした後のデータベースのコレクション。`_id`というのはmongoDBがそのドキュメントを一意に決めるために勝手につけたもの（今は無視）。

```
> db.users.find()
{ "_id" : ObjectId("5defa2febf45783f767a986c"), "name" : "山田", "score" : 30 }
{ "_id" : ObjectId("5defa86cbf45783f767a986d"), "score" : 0.02110447136564797 }
{ "_id" : ObjectId("5defa86cbf45783f767a986e"), "score" : 0.440074390405233 }
{ "_id" : ObjectId("5defa86cbf45783f767a986f"), "score" : 0.4986839719651923 }
{ "_id" : ObjectId("5defa86cbf45783f767a9870"), "score" : 0.5104256306621539 }
{ "_id" : ObjectId("5defa86cbf45783f767a9871"), "score" : 0.6623533511869121 }
{ "_id" : ObjectId("5defa86cbf45783f767a9872"), "score" : 0.6347502713379466 }
{ "_id" : ObjectId("5defa86cbf45783f767a9873"), "score" : 0.7084804364242808 }
{ "_id" : ObjectId("5defa86cbf45783f767a9874"), "score" : 0.741579757873828 }
{ "_id" : ObjectId("5defa86cbf45783f767a9875"), "score" : 0.8656916597355904 }
{ "_id" : ObjectId("5defa86cbf45783f767a9876"), "score" : 0.23169459671355885 }
```

第一引数にオブジェクトを渡すと該当するコレクションのみをリストアップできる。teamというコレクションのある例で見てみる。第一引数に`{team:"team-1"}`を与えたことで、それに該当する2件のみがリストアップされた。

```
> db.users.find()
{ "_id" : ObjectId("5defbcb7a9066b4dd5a6fc56"), "name" : "山田", "score" : 30, "team" : "team-1" }
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
{ "_id" : ObjectId("5defbe6ba9066b4dd5a6fc58"), "name" : "佐藤", "score" : 14, "team" : "team-1" }

> db.users.find({team:"team-1"})
{ "_id" : ObjectId("5defbcb7a9066b4dd5a6fc56"), "name" : "山田", "score" : 30, "team" : "team-1" }
{ "_id" : ObjectId("5defbe6ba9066b4dd5a6fc58"), "name" : "佐藤", "score" : 14, "team" : "team-1" }
```

スコアの成績で絞るならscoreの値を`{$gte:50}`と指定することで「50点以上」のものがリストアップされる。`$gte`とはGrater Than or Equalの略。つまり`$gt`とすればGrater Thanの略ということで「50を超えるもの」に絞られる。

- 以上：Grater Than or Equal：{$gte}
- 以下：Less Than or Equal：{$lte}
- 超過：Grater Than：{$gt}
- 未満：Less Than：{$ls}
- 等価：Equal：{$eq}
- 不等価：Not Equal：{$nq}

さきほど第一引数に`{team:"team-1"}`を渡したものも`{team:{$eq:team-1}}`と同義ということになる。

また、JavaScriptの正規表現も使える。例えば、名前に「t」を含むものに絞るなら`{name: /t/}`とし、前方一致で絞るなら`{name: /^t/}`とする。これらを組み合わせて複雑な条件で検索できる。

andで条件を列挙する方法。下記の例ではnameフィールドに「田」が含まれ、かつ、scoreフィールドが50以上のコレクションを抽出するという意味になる。検索条件をandでつなぐ場合は`,`で区切ればいい。

```
> db.users.find({name:/田/, score:{$gte:50}})
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
```

orで条件を列挙する方法。下記の例では、naeフィールドに田が含まれる、もしくは、scoreフィールドが20以上のものに絞られる。書き方は`db.users.find()`の第一引数に`{$or {}, {}}`と条件を列挙するだけ。

```
> db.users.find({$or: [{name:/田/}, {score:{$gte:20}}]})
{ "_id" : ObjectId("5defbcb7a9066b4dd5a6fc56"), "name" : "山田", "score" : 30, "team" : "team-1" }
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
```

さらに、同じフィールド内で複数条件で絞るには第一引数に`{score: {$in: [59, 14]}}`を与えることで「スコアが59か14のもの」に絞れる。この`{$in: []}`という配列に条件を列挙すればいい。

```
> db.users.find({score: {$in: [14, 59]}})
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
{ "_id" : ObjectId("5defbe6ba9066b4dd5a6fc58"), "name" : "佐藤", "score" : 14, "team" : "team-1" }
```

さらにさらに、特定のフィールドを持つコレクションのみを抽出するならfind()の第一引数に`{fieldName: {$exists: true}}`というオブジェクトを渡せばいい。例えば以下のようなコレクションがあったとして、ageフィールドが含まれるコレクションのみを抽出する場合`find({age: {$exists: true}})`となる。

```
> db.users.find()
{ "_id" : ObjectId("5defbcb7a9066b4dd5a6fc56"), "name" : "山田", "score" : 30, "team" : "team-1" }
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
{ "_id" : ObjectId("5defbe6ba9066b4dd5a6fc58"), "name" : "佐藤", "score" : 14, "team" : "team-1" }
{ "_id" : ObjectId("5defcaf6a9066b4dd5a6fc59"), "name" : "竹中", "score" : 96, "age" : 23 }

> db.users.find({age: {$exists: true}})
{ "_id" : ObjectId("5defcaf6a9066b4dd5a6fc59"), "name" : "竹中", "score" : 96, "age" : 23 }
```

ちなみにこの`true`は`1`でも代用できる。`createCollection()`や`insert()`の実行結果として`{ "ok" : 1 }`や`WriteResult({ "nInserted" : 1 })`が返っているのは成功したという意味。逆に`false`は`0`で代用できる。つまり`finde({age: {#exists: 0}})`とするとageフィールドがないコレクションのみが抽出される。

###特定のフィールドの値を一覧する　db.colName.distinct()

```
> db.users.distinct("team")
[ "team-1", "team-2" ]
```

```
> db.users.find(score:{$gte:50})
{ "_id" : ObjectId("5defbe37a9066b4dd5a6fc57"), "name" : "田中", "score" : 59, "team" : "team-2" }
```

---

###データベースの情報を見る　db.stats()

意味が分かるようになっておいた方が良い。`"db" : "mydb"`の部分でdb名が確認出来たり。

```
> db.stats()
{
	"db" : "mydb",
	"collections" : 1,
	"views" : 0,
	"objects" : 0,
	"avgObjSize" : 0,
	"dataSize" : 0,
	"storageSize" : 4096,
	"numExtents" : 0,
	"indexes" : 1,
	"indexSize" : 4096,
	"scaleFactor" : 1,
	"fsUsedSize" : 222645305344,
	"fsTotalSize" : 250685575168,
	"ok" : 1
}
```

#Node.jsでmongoDBに接続する手順

>[MongoDB Node.js Drive](https://docs.mongodb.com/ecosystem/drivers/node/)
>[Connect to MongoDB](http://mongodb.github.io/node-mongodb-native/3.2/tutorials/connect/)
>[Quick Start](http://mongodb.github.io/node-mongodb-native/3.1/quick-start/quick-start/)

#エラーで困ったときの覚書

###「shutting down with code:48」と言われた

```
$ sudo mongod
Password:
2019-12-13T23:26:26.872+0900 I  CONTROL  [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
2019-12-13T23:26:26.875+0900 I  CONTROL  [initandlisten] MongoDB starting : pid=81719 port=27017 dbpath=/data/db 64-bit host=MacBookPro.local
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] db version v4.2.1
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] git version: edf6d45851c0b9ee15548f0f847df141764a317e
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] allocator: system
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] modules: none
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] build environment:
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten]     distarch: x86_64
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten]     target_arch: x86_64
2019-12-13T23:26:26.876+0900 I  CONTROL  [initandlisten] options: {}
2019-12-13T23:26:26.877+0900 E  STORAGE  [initandlisten] Failed to set up listener: SocketException: Address already in use
2019-12-13T23:26:26.877+0900 I  CONTROL  [initandlisten] now exiting
2019-12-13T23:26:26.877+0900 I  CONTROL  [initandlisten] shutting down with code:48
```

ひとつだけ`E`が表示されている行がある。

```
2019-12-13T23:26:26.877+0900 E  STORAGE  [initandlisten] Failed to set up listener: SocketException: Address already in use
```

この部分を直訳すると「listenerのセットアップ失敗。ソケット例外。すでに使用されているアドレス」てな感じで、つまり「mongoDBが別で使われてるから接続失敗やで」ということ。

mongoDBの状態を確認する↓

```
$ brew services list

Name              Status  User      Plist
mongodb-community started user-name /Users/user-name/Library/LaunchAgents/homebrew.mxcl.mongodb-community.plist
```

すでに他で使われてないか確認する↓

```
$ ps -ef | grep mongod

  501 61483     1   0 火08PM ??         9:37.50 /usr/local/opt/mongodb-community/bin/mongod --config /usr/local/etc/mongod.conf
  501 81673 80188   0 11:00PM ttys003    0:00.00 grep mongod
```

よくわからんのでとりあえず両方`kill`してみたところ、

```
$ sudo kill 80188
Password:

$ sudo kill 61483

$ ps -ef | grep mongod
  501 81751 80188   0 11:36PM ttys003    0:00.00 grep mongod
```

片方生き残った。`mongod.conf`の方がやられたっぽい。その上で`mongod`やると、

```
$ sudo mongod --dbpath db
2019-12-13T23:37:54.920+0900 I  CONTROL  [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
2019-12-13T23:37:54.925+0900 I  CONTROL  [initandlisten] MongoDB starting : pid=81759 port=27017 dbpath=db 64-bit host=MacBookPro.local
2019-12-13T23:37:54.925+0900 I  CONTROL  [initandlisten] db version v4.2.1
…
```

と、50行くらいのログが出て接続成功して待機状態に入った。ただ、そのログのどこにも「connections」的な単語がなかったので怪しい。パスを通した空のディレクトリの`db/`に大量のファイルが生成されたのでできてるような気もする。

<img width="405" alt="スクリーンショット 2019-12-13 23.55.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278293/27d80ef3-e08b-006f-f9d5-4477ed502e8f.png">


結局確信は持てなかったが、別のカレントディレクトリで`mongod`やったものが生きてて邪魔していたのだろうと推測。ググっても一致する事例がなく、ログのメッセージがmongoDBのバージョンで変わったのかも知れない。一応そういう仮説で調べてみようとは思う。