Mongo Shell 徹底入門 基礎編

会社の本番環境でMongoDBを使用していて、運用の中でMongo Shellをよく使うのでtipsをまとめてみました。

基礎編なので今更な内容も多いかと思いますが、実際に使用しててこれだけ覚えておけば最低限なんとかなるかなみたいなものをまとめてます。

内容が間違ってるとかありましたらコメントや編集リクエストを送ってください。

## `findOne()`

1件だけデータを取得する場合は `findOne()` を使うのが便利です。
`findOne()` に取得したいデータの条件を入れれば条件にマッチするドキュメントを取得することができます。

この条件の部分がSQLのWHERE句に当たります。

```javascript
> db.users.findOne({_id: ObjectId("56e97630e62ae590e0d545a3")})
{
  "_id" : ObjectId("56e97630e62ae590e0d545a3"),
  "name" : "sample-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:05:10.925Z"),
  "updated_at" : ISODate("2016-03-16T15:05:10.925Z")
}
```

また条件をobjectではなく、`ObjectId()` を入れるとドキュメントの `_id` が指定した `ObjectId()` のドキュメントを取得できます。

```javascript
> db.users.findOne(ObjectId("56e97630e62ae590e0d545a3"))
{
  "_id" : ObjectId("56e97630e62ae590e0d545a3"),
  "name" : "sample-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:05:10.925Z"),
  "updated_at" : ISODate("2016-03-16T15:05:10.925Z")
}
```

## `find()`

データを複数件取得したい場合は `find()` を使用します。

```javascript
> db.users.find()
{ "_id" : ObjectId("56e97630e62ae590e0d545a3"), "name" : "sample-user", "password" : "password", "created_at" : ISODate("2016-03-16T15:05:10.925Z"), "updated_at" : ISODate("2016-03-16T15:05:10.925Z") }
{ "_id" : ObjectId("56e97834e62ae590e0d545a4"), "name" : "sample-user2", "password" : "password", "created_at" : ISODate("2016-03-16T15:13:54.230Z"), "updated_at" : ISODate("2016-03-16T15:13:54.230Z") }
```

普通に `find()` しただけだと↑のようにデータが見難いので、 `find()` の結果の後に `pretty()` か `toArray()` をつけるとデータが見やすくなります。

```javascript
> db.users.find().pretty()
{
  "_id" : ObjectId("56e97630e62ae590e0d545a3"),
  "name" : "sample-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:05:10.925Z"),
  "updated_at" : ISODate("2016-03-16T15:05:10.925Z")
}
{
  "_id" : ObjectId("56e97834e62ae590e0d545a4"),
  "name" : "sample-user2",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:13:54.230Z"),
  "updated_at" : ISODate("2016-03-16T15:13:54.230Z")
}
> db.users.find().toArray()
[
  {
    "_id" : ObjectId("56e97630e62ae590e0d545a3"),
    "name" : "sample-user",
    "password" : "password",
    "created_at" : ISODate("2016-03-16T15:05:10.925Z"),
    "updated_at" : ISODate("2016-03-16T15:05:10.925Z")
  },
  {
    "_id" : ObjectId("56e97834e62ae590e0d545a4"),
    "name" : "sample-user2",
    "password" : "password",
    "created_at" : ISODate("2016-03-16T15:13:54.230Z"),
    "updated_at" : ISODate("2016-03-16T15:13:54.230Z")
  }
]
```

## 演算子

MongoDB の `find()` で使える演算子は
https://docs.mongodb.org/manual/reference/operator/query/
とかにまとまってますが、個人的には以下の演算子を覚えておけばだいたい困らないのではないかと思います。


演算子 | 用途
--- | ---
`$ne` | SQLでいうところの `!=`
`$lt` | SQLでいうところの `<`
`$lte` | SQLでいうところの `<=`
`$gt` | SQLでいうところの `>`
`$gte` | SQLでいうところの `>=`
`$exists` | フィールド存在チェック `{ フィールド名: { $exists: true } }` でそのフィールドがあるものに絞られる( false ならないもの )
`$in` | `$in` で指定した配列の値のいずれかと同じ値のドキュメントがマッチする
`$nin` | `$nin` で指定した配列の値のいずれとも同じ値ではないドキュメントがマッチする
`$or` | SQLでいうところの `OR` 配列で条件を複数渡せる
`$and` | SQLでいうところの `AND` 配列で条件を複数渡せる

下記に`$in` とかわかりにくいそうなものをクエリにしてみました。

### `$in`

```javascript
> db.users.find({ name: { $in: [ "sample-user", "sample-user-deleted" ] } })
{ "_id" : ObjectId("56e97630e62ae590e0d545a3"), "name" : "sample-user", "password" : "password", "created_at" : ISODate("2016-03-16T15:05:10.925Z"), "updated_at" : ISODate("2016-03-16T15:05:10.925Z") }
{ "_id" : ObjectId("56e97e7fe62ae590e0d545a5"), "name" : "sample-user-deleted", "password" : "password", "created_at" : ISODate("2016-03-16T15:40:43.309Z"), "updated_at" : ISODate("2016-03-16T15:40:43.309Z"), "flags" : { "deleted" : true } }
```

### `$or`

`$and` もおんなじような感じです。

```javascript
> db.users.find({ $or: [ { "flags.deleted": true }, { name: "sample-user" }  ] })
{ "_id" : ObjectId("56e97630e62ae590e0d545a3"), "name" : "sample-user", "password" : "password", "created_at" : ISODate("2016-03-16T15:05:10.925Z"), "updated_at" : ISODate("2016-03-16T15:05:10.925Z") }
{ "_id" : ObjectId("56e97e7fe62ae590e0d545a5"), "name" : "sample-user-deleted", "password" : "password", "created_at" : ISODate("2016-03-16T15:40:43.309Z"), "updated_at" : ISODate("2016-03-16T15:40:43.309Z"), "flags" : { "deleted" : true } }
{ "_id" : ObjectId("56e984fee62ae590e0d545a7"), "name" : "saved-user", "password" : "password", "created_at" : ISODate("2016-03-16T16:08:25.338Z"), "updated_at" : ISODate("2016-03-16T16:08:25.338Z"), "flags" : { "deleted" : true } }
```

## `forEach()`

`find()` した結果では `forEach()` が使えます。
ちょっとしたデータを加工したりするのに便利です。

下記では `find()` した結果をCSVで出力しています。

```javascript
> db.users.find().forEach(function(v) { print((Object.keys(v).map(function(k) { return v[k]; } )).join(",")); })
ObjectId("56e97630e62ae590e0d545a3"),sample-user,password,Thu Mar 17 2016 00:05:10 GMT+0900 (JST),Thu Mar 17 2016 00:05:10 GMT+0900 (JST)
ObjectId("56e97834e62ae590e0d545a4"),sample-user2,password,Thu Mar 17 2016 00:13:54 GMT+0900 (JST),Thu Mar 17 2016 00:13:54 GMT+0900 (JST)
```

## `map()`

`find()` ではmapメソッドを使うこともできます。

あまり現実的ではありませんが、 `map()` で必要な列のデータだけ取得・加工して、 `forEach()` で結果をCSVで出力する場合は以下のようになります。

```javascript
> db.users.find().map(function(v) { return { id: v._id.valueOf(), name: v.name, password: v.password }; }).forEach(function(v) { print((Object.keys(v).map(function(k) { return v[k]; } )).join(",")); })
56e97630e62ae590e0d545a3,sample-user,password
56e97834e62ae590e0d545a4,sample-user2,password
```

## `toArray()`

`find()` でもさりげなく使用していましたが、 `find()` の結果に `toArray()` を使うと `find()` した結果を配列に変換できます。

`find()` した結果そのままでは `forEach()` , `map()` メソッドあたりしか使えず、取得した結果に対して `reduce()` や `filter()` メソッドなどを使いたい場合は `toArray()` する必要があります

これもまったく現実的ではないですが、 `find()` した結果を `reduce()` してドキュメントの件数を数える場合は以下のようになります。

```javascript
> db.users.find().toArray().reduce(function(count, v) { return ++count; }, 0)
2
```

## `count()`


上記の `toArray()` ではわざわざ `reduce()` を使用して行いましたが、単純にマッチする条件のドキュメントの件数を数えるだけであれば `count()` が使用できます。

例えば `name` が `"sample-user"` のユーザー件数を取得するなら以下のようになります。

```javascript
> db.users.count({ name: "sample-user" })
1
```


## `distinct()`

コレクション(RDBでのテーブル)内の特定のフィールド(RDBでの列)の値の一覧を取得するには `distinct()` が使えます。

例えば `users` コレクションの `name` フィールド の値の一覧を取得する場合は以下のようになります。

```javascript
> db.users.distinct("name")
[ "sample-user", "sample-user2", "sample-user-deleted" ]
```

ちなみにMongoDBの用語についての参考は
http://mironal-memo.blogspot.jp/2012/07/mongodb-word.html

## `insert()`

コレクションにドキュメントを追加するには `insert()` を使用します。

```javascript
> db.users.insert({name: "insert-user", password: "password", created_at: ISODate(), updated_at: ISODate() })
WriteResult({ "nInserted" : 1 })
> db.users.findOne({ name: "insert-user" })
{
  "_id" : ObjectId("56e9807ee62ae590e0d545a6"),
  "name" : "insert-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:49:18.887Z"),
  "updated_at" : ISODate("2016-03-16T15:49:18.887Z")
}
```

## `update()`

ドキュメントの内容を更新するには `update()` を使用します。
`update()` の第一引数に更新大小のドキュメントの条件を、第二引数に更新する内容渡します。

```javascript
> db.users.update({"_id" : ObjectId("56e9807ee62ae590e0d545a6")}, {name: "updated-user", password: "password", created_at: ISODate(), updated_at: ISODate() })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne({ name: "updated-user" })
{
  "_id" : ObjectId("56e9807ee62ae590e0d545a6"),
  "name" : "updated-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:52:13.623Z"),
  "updated_at" : ISODate("2016-03-16T15:52:13.623Z")
}
```

ただし、`update()` は `$set` 修飾子を使用しないと第二引数に渡したobjectの内容で全ての内容を上書きしてしまいます。

例えば、ユーザーのドキュメントに削除フラグを追加するような場合には以下のようにする必要があります。

```javascript
> db.users.update({"_id" : ObjectId("56e9807ee62ae590e0d545a6")}, { $set: { flags: { deleted: true } } })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne({ name: "updated-user" })
{
  "_id" : ObjectId("56e9807ee62ae590e0d545a6"),
  "name" : "updated-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T15:52:13.623Z"),
  "updated_at" : ISODate("2016-03-16T15:52:13.623Z"),
  "flags" : {
    "deleted" : true
  }
}
```

`update()` について詳しいことは
http://qiita.com/saba1024/items/f2ad56f2a3ba7aaf8521#update%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89---%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E6%9B%B4%E6%96%B0
あたりが詳しいのでこちらを読んだほうが良いと思います。


## `remove()`

作成したドキュメントを削除する場合は `remove()` を使用します。

```javascript
> db.users.remove({ _id : ObjectId("56e9807ee62ae590e0d545a6") })
WriteResult({ "nRemoved" : 1 })
> db.users.findOne(ObjectId("56e9807ee62ae590e0d545a6"))
null
```

## `save()`

先ほど `insert()` , `update()` を紹介しましたが、実際に業務でドキュメントを作成したり更新したりするのはほとんど `save()` を使っています。
`save()` では引数に渡したobjectを使ってドキュメントの作成・更新ができます。

例えばユーザーを作成して、削除したユーザーに削除フラグを追加する、みたいな操作はこんな感じになります。

```javascript
> var user = { name: "saved-user", password: "password", created_at: ISODate(), updated_at: ISODate() }
> db.users.save(user);
WriteResult({ "nInserted" : 1 })
> user
{
  "name" : "saved-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T16:08:25.338Z"),
  "updated_at" : ISODate("2016-03-16T16:08:25.338Z"),
  "_id" : ObjectId("56e984fee62ae590e0d545a7")
}
> db.users.findOne(ObjectId("56e984fee62ae590e0d545a7"))
{
  "_id" : ObjectId("56e984fee62ae590e0d545a7"),
  "name" : "saved-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T16:08:25.338Z"),
  "updated_at" : ISODate("2016-03-16T16:08:25.338Z")
}
> var savedUser = db.users.findOne(ObjectId("56e984fee62ae590e0d545a7"))
> savedUser.flags = { deleted: true }
{ "deleted" : true }
> db.users.save(savedUser);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne(ObjectId("56e984fee62ae590e0d545a7"))
{
  "_id" : ObjectId("56e984fee62ae590e0d545a7"),
  "name" : "saved-user",
  "password" : "password",
  "created_at" : ISODate("2016-03-16T16:08:25.338Z"),
  "updated_at" : ISODate("2016-03-16T16:08:25.338Z"),
  "flags" : {
    "deleted" : true
  }
}
```

## 変数

`save()` の時にさりげなく使ってましたが、 Mongo Shell では普通に javascript がかけるのでもちろん変数が使えます。
なので例えば

```javascript
> var flag = true
> db.users.find().forEach(function(v) { flag = false })
> flag
false
```

みたいな感じで変数が使えます

## `print()`, `printjson()`

これも `find()` の時にさりげなく使ってましたが、Mongo Shell だと値の出力は `console.log()` ではなく `print()`, `printjson()` を使って出力します。

```javascript
> print({})
[object Object]
> printjson({})
{ }
> print("print string")
print string
> printjson("printstring")
"printstring"
> print([1, 2])
1,2
> printjson([1, 2])
[ 1, 2 ]
```

こんな感じで `print()` だと objectの中身を出力できないので、objectの値を出力したいときは `printjson()` を使うと良いと思います。

## `sort()`

`find()` した結果をソートするには `sort()` を使用します。
`sort()` にはソートに使用したいキーと昇順・降順の指定を渡します。

```javascript
> db.users.find().sort({created_at: 1}).map(function(v) { return v.created_at;  })
[
  ISODate("2016-03-16T15:05:10.925Z"),
  ISODate("2016-03-16T15:13:54.230Z"),
  ISODate("2016-03-16T15:40:43.309Z"),
  ISODate("2016-03-16T16:08:25.338Z")
]

> db.users.find().sort({created_at: -1}).map(function(v) { return v.created_at;  })
[
  ISODate("2016-03-16T16:08:25.338Z"),
  ISODate("2016-03-16T15:40:43.309Z"),
  ISODate("2016-03-16T15:13:54.230Z"),
  ISODate("2016-03-16T15:05:10.925Z")
]
```

## `limit()`

`find()` したドキュメントから何件のドキュメントを取得するのかを指定するには `limit()` を使用します。
(SQLのLIMIT句と同じです)

```javascript
> db.users.find().limit(2)
{ "_id" : ObjectId("56e97630e62ae590e0d545a3"), "name" : "sample-user", "password" : "password", "created_at" : ISODate("2016-03-16T15:05:10.925Z"), "updated_at" : ISODate("2016-03-16T15:05:10.925Z") }
{ "_id" : ObjectId("56e97834e62ae590e0d545a4"), "name" : "sample-user2", "password" : "password", "created_at" : ISODate("2016-03-16T15:13:54.230Z"), "updated_at" : ISODate("2016-03-16T15:13:54.230Z") }
```

## `skip()`

`find()` したドキュメントからx件目以降のドキュメントを取得するのかを指定するには `skip()` を使用します。
(SQLのOFFSET句と同じです)

```javascript
> db.users.find().skip(2)
{ "_id" : ObjectId("56e97e7fe62ae590e0d545a5"), "name" : "sample-user-deleted", "password" : "password", "created_at" : ISODate("2016-03-16T15:40:43.309Z"), "updated_at" : ISODate("2016-03-16T15:40:43.309Z"), "flags" : { "deleted" : true } }
{ "_id" : ObjectId("56e984fee62ae590e0d545a7"), "name" : "saved-user", "password" : "password", "created_at" : ISODate("2016-03-16T16:08:25.338Z"), "updated_at" : ISODate("2016-03-16T16:08:25.338Z"), "flags" : { "deleted" : true } }
```

ページネーションでページによって取得するドキュメントの位置をズラすには `limit()` と `skip()` を使用して

```javascript
> db.users.find().skip(0).limit(2)
{ "_id" : ObjectId("56e97630e62ae590e0d545a3"), "name" : "sample-user", "password" : "password", "created_at" : ISODate("2016-03-16T15:05:10.925Z"), "updated_at" : ISODate("2016-03-16T15:05:10.925Z") }
{ "_id" : ObjectId("56e97834e62ae590e0d545a4"), "name" : "sample-user2", "password" : "password", "created_at" : ISODate("2016-03-16T15:13:54.230Z"), "updated_at" : ISODate("2016-03-16T15:13:54.230Z") }
> 
> db.users.find().skip(2).limit(2)
{ "_id" : ObjectId("56e97e7fe62ae590e0d545a5"), "name" : "sample-user-deleted", "password" : "password", "created_at" : ISODate("2016-03-16T15:40:43.309Z"), "updated_at" : ISODate("2016-03-16T15:40:43.309Z"), "flags" : { "deleted" : true } }
{ "_id" : ObjectId("56e984fee62ae590e0d545a7"), "name" : "saved-user", "password" : "password", "created_at" : ISODate("2016-03-16T16:08:25.338Z"), "updated_at" : ISODate("2016-03-16T16:08:25.338Z"), "flags" : { "deleted" : true } }
```

のような感じにすればできます。