MongoDBクエリなどいろいろ

実務でNoRDB、DocumentのデータストアとしてMongoDBを利用しています。
実装、調査、マイグレーションにおいてMongoDBのクエリを色々作成しておりよく使用する
基本的なクエリ操作についてメモしたいと思います。

#1 データ構造

```json
データベース　db
＋ーーコレクション collection
｜　　　＋ドキュメント document
｜　　　｜　　{
｜　　　｜　　　"_id" : ObjectId("54bc6d910cf29e7da446d9ef"),
｜　　　｜　　　"変数名" : 値
｜　　　｜　　　　　・
｜　　　｜　　　　　・
｜　　　｜　　}
｜　　　＋ドキュメント document
｜　　　｜　　{
｜　　　｜　　　"_id" : ObjectId("54bc6d910cf29e7da446d9ed"),
｜　　　｜　　　　　・
｜　　　｜　　　　　・
｜　　　｜　　}
｜　　　　・
｜　　　　・
データベース　db
＋ーーコレクション collection
｜　　　＋ドキュメント document
｜　　　＋ドキュメント document
｜　　・
｜　　・
```

#2 挿入　insert

- **単数の挿入**

json形式のdocumentをそのまま保存できます。
(16MByte/documntが最大のようです)


```js
$> db.color.insert(
{
"name" : "black" ,
"nameJpn" : "黒" ,
"hexCode" : "#000000",
"r" : 0,
"g" : 0,
"b" : 0,
}
)
```
- **複数の挿入**

```js
$> db.color.insert([
{"name" : "black" "nameJpn" : "黒", "hexCode" : "#000000","r" : 0,"g" : 0 ,"b" : 0},
{"name" : "gray" "nameJpn" : "灰", "hexCode" : "#808080","r" : 128,"g" : 128 ,"b" : 128},
{"name" : "silver" "nameJpn" : "銀", "hexCode" : "#C0C0C0","r" : 192,"g" : 192 ,"b" : 192},
{"name" : "white" "nameJpn" : "白", "hexCode" : "#FFFFFF","r" : 255,"g" : 255 ,"b" : 255},
{"name" : "blue" "nameJpn" : "青", "hexCode" : "#0000FF","r" : 0,"g" : 0 ,"b" : 128},
{"name" : "navy" "nameJpn" : "ネイビー", "hexCode" : "#000080","r" : 0,"g" : 0 ,"b" : 128},
{"name" : "teal" "nameJpn" : "かものは", "hexCode" : "#008080","r" : 0,"g" : 128 ,"b" : 128},
{"name" : "green" "nameJpn" : " 緑", "hexCode" : "#008000","r" : 0,"g" : 128 ,"b" : 0},
{"name" : "lime" "nameJpn" : "ライム", "hexCode" : "#00FF00","r" : 0,"g" : 256 ,"b" : 0},
{"name" : "aqua" "nameJpn" : "水", "hexCode" : "#00FFFF","r" : 0,"g" : 256 ,"b" : 256},
{"name" : "yellow" "nameJpn" : "黄", "hexCode" : "#FFFF00","r" : 255,"g" : 256 ,"b" : 0},
{"name" : "red" "nameJpn" : " 赤", "hexCode" : "#FF0000",r : 255,"g" : 0 ,"b" : 0},
{"name" : "fuchsia" "nameJpn" : "フクシア", "hexCode" : "#FF00FF","r" : 255,"g" : 0 ,"b" : 256},
{"name" : "olive" "nameJpn" : "オリーブ", "hexCode" : "#808000","r" : 128,"g" : 128 ,"b" : 0},
{"name" : "purple" "nameJpn" : "紫", "hexCode" : "#800080","r" : 128,"g" : 0 ,"b" : 128},
{"name" : "maroon" "nameJpn" : "マルーン", "hexCode" : "#800000","r" : 128,"g" : 0,"b" : 0}
])
```

#3 抽出　find

基本形　findの（）中に検索条件を記載していく
```$> db.targetCollection.find({"value":"999"})```

## 値の階層表現

bodyのした階層のheightを指定
```$> db.helthcheck.find( { "body.height": 150 } )```
３回目の測定のbodyのした階層のheightを指定
```$> db.helthcheck.find( { "body.2.height": 150 } )```

## 値の評価（= != < > <= >=）

| 演算子 | MongoDB | 概要 | サンプル |
|:--|:--|:--|:--|
|value = 値| : | 等しい | { value:1000 } |
|value < 値| $lt | 右辺より小さい | { value:{$lt:1000} } |
|value <= 値| $lte | 右辺以下 | { value:{$lte:1000} } |
|value > 値| $gt | 右辺より大きい | { value:{$gt:1000} } |
|value >= 値| $gte | 右辺以上 | { value:{$gte:1000} } |
|value != 値| $ne | 等しくない | { value:{$ne:1000} } |

## 条件連結

|演算|MongoDB|
|:--|:--|
|AND| , つなぎもしくは$and|
|OR|$or|

```AND.js
$> db.client.find( {"lastName":"太郎","zip":"910-3099"}
```

```OR.js
$> db.client.find( $or: [{ age: { $lt: 18 } },{ age: { $gte: 65 } }] })
```

```AND_OR.js
$> db.client.find( {
      $and : [
               { 
                 $or : [ 
                         {"firstName" : "佐藤"},
                         {"lastName" : "太郎"}
                       ]
               },
               { 
                 "Phone":"03-1234-5678"
               }
             ]
})
```

## 値の複数値一致(in句)

```$> db.targetCollection.find( { "clientId": { $in: [ "1234", "3454", "4567"] } } )```

## 値の存在有無

```$> db.targetCollection.find({ "createDate":{$exists:false} })```

## distinct

```$> db.targetCollection.distinct("distinctKey", "createDate":{$exists:false}})```

## ObjenctIdの一致

```$> db.targetCollection.find({"_id": {"$oid": "34917234623847612984"}}})```
もしくは
```$> db.targetCollection.find({"_id": ObjectId("34917234623847612984")}})```

## 日時の比較

年月日を取り出す

日付範囲取得 +9でJSTにしている
```$> db.targetCollection.find({ "timestamp" : { "$gte" : ISODate("2019-11-01T00:00:00+09:00"), "$lte" : ISODate("2019-11-02T00:00:00+09:00") } })```

## 文字列の部分一致(like)

"camera"という文字が入った商品を取り出す
```$> db.targetCollection.find({ "name" : /camera/ })```
後方のみ　camerahoge
```$> db.targetCollection.find({ "name" : /^camera/ })```

## 件数取得

```$> db.targetCollection.find({"value":"999"}).count()```

## ソート

1:昇順、-1：降順
```$> db.targetCollection.find().sort({'value1':1, 'value2':-1})```

## 最小、最大、平均、合計

```kindごと合計.js
$> db.product.aggregate([
    {
      $group: {
        _id: "$kind",
        total: { $sum: "$price" }
      }
    }
])
```

```値段の最大　最小サンプル.js
$> db.product.aggregate([ 
    { "$group": { 
        "_id": null,
        "max": { $max: "$price" }, 
        "min": { $min: "$price" } 
    }}
])
```
```値段の平均サンプル.js
$> db.product.aggregate（[
    { "$group": { 
        "_id": null,
        "avg": { $avg: "$price" } 
    }}
])
```

#4 更新　update

## 部分更新

```$> db.client.update( { "Name": "aoki" }, { $set: { "age": 67 } },false,false)```
updateの最後の２つのフラグの意味
最後２番目フラグ・・・falseはupsert(レコードが存在していなければINSERT, 存在すればUPDATE)  省略デフォルトはfalse
最後のフラグ・・・・・・trueはmulti

## 全更新

```$> db.client.update({},{ $set: { "targetflag" : "true"} },false,true)```

## 配列の更新

18才のひとの選挙をtrueにする例
```$> db.client.update({"clientinfo.year":{"egt":18}},{$set:{"clientinfo.$.senkyo":"true"}},false,true)```

## フィールド名の変更
```$> db.client.update({}, { $rename : { "oldName" : "newName" } });```

#5 削除 delete

## object削除

```$> db.collection.remove({"_id" : ObjectId("4da18d731e1d64c1023a1ad7")})```
条件を入れないと全削除になるので注意

## 部分削除

コレクションの中身の一箇所を削除したい
```$> db.client.update( { "Name": "aoki" }, { $unset: { "age": 1 } })```
全部の特定フィールドを全削除
```$> db.client.update({},{ $unset: { "targetDeletefield" : 1} },false,true)```

## collection削除

```$> db.collection.drop()```

## db削除

```$> use dbname
$> db.dropDatabase()```

#6 その他

## rename

コレクションの名前変更
```$> db.oldCollection.renameCollection('newCollection')```

## copy

Databaseコピー
```$> db.copyDatabase('fromDatabase', 'toDatabase')```

コレクションのコピー
```$> db.oldCollection.find().forEach(function (x) {db.newCollection.save(x)})```

## indexの作成

```$> db.infomation.createIndex( { "createDate": -1, "userId": 1, "printType": 1} )```
-1:が降順　1:が昇順

削除
```$> db.infomation.dropIndex({ "createDate": -1, "userId": 1, "printType": 1})```
条件指定しなければ全インデックス削除

## 実行計画

explain()　をつける

```js
$> db.collection.find("clientId":"1735").explain()
{
    "cursor" : "BtreeCursor clientId_1_reportType_1",
    "isMultiKey" : false,
    "n" : 7,
    "nscannedObjects" : 7,
    "nscanned" : 7,
    "nscannedObjectsAllPlans" : 7,
    "nscannedAllPlans" : 7,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "clientId" : [ 
            [ 
                "1735", 
                "1735"
            ]
        ],
        "reportType" : [ 
            [ 
                {
                    "$minElement" : 1
                }, 
                {
                    "$maxElement" : 1
                }
            ]
        ]
    }
}
```
"cursor" : "BtreeCursor clientId_1_reportType_1"　　clientId_1_reportType_1のindexが使用されていることがわかる
"nscanned" : 7　　　　 　　7つスキャンして
"nscannedObjects" : 7　　　そのまま7つ読んで調べました

| 項目 | 解説|
|:--|:--|
| cursor | カーソルタイプ。BasicCursor、BtreeCursor、GeoSearchCursor |
| isMultiKey | マルチキーインデックスを使っているか |
| n | 最終的にヒットしたドキュメント件数 |
| nscannedObjects | スキャンされたオブジェクトの数 |
| nscanned | スキャンされたオブジェクトorインデックスの数 |
| nscannedObjectsAllPlans | すべてのクエリでスキャンされたオブジェクトの総数 |
| nscannedAllPlans | すべてのクエリでスキャンされたオブジェクトorインデックスの数 |
| scanAndOrder | インデックスを使わずに結果をソートして返すか |
| indexOnly | クエリがインデックスのみを使っているか |

## クエリログ調査

ログレベル確認
```$> db.getProfilingLevel()```
もしくは
```$> db.setLogLevel(-1, "query")```

ログレベル設定
```$> db.setProfilingLevel(2)```
もしくは
```$> use admin
$> db.adminCommand({setParameter:1, logLevel:2})```

ログレベルの種類　
レベルの詳細がいまいちわからなかった

| Level       　　　|       解説 　　　　|    その他    　　　　　|
|:-----------------|------------------:|:------------------:|
| Level 0　　　　　 | 　　|一番小さい|
| Level 1　　　　　 | 　||
| Level 2　　　　　 | 　Query を確認できる　||
| Level 3　　　　　 | 　　||
| Level 4　　　　　 | 　　||
| Level 5　　　　　 | 　　|一番大きい|
※logファイルのサイズがでかくなるのでディスクの容量を気にするのと必ず元に戻すことを忘れずに

ログ出力
```$> db.system.profile.find().pretty()```

ログの抽出
```$> db.system.profile.find({"query.user_id":10082, "ts": {"$gt": ISODate("2019-11-27T12:13:25.435Z")}});```
userと日付で抽出のケース

#7 簡単なjs

```migration.js
db = db.getSiblingDB('test');
print("Start");
var clientCount=0;
var beforeIt = 0;
db.datacollection.find({"dataCreateDate":{$exists: false}}).forEach(function(document) {
  var id = document._id;
  print("id:" + id);
  print("clientId:" + document.clientId);
  db.datacollection.update({"_id" : id}, {"$set": { "dataCreateDate" : document.lastUpdateDate}}, false, false);
  clientCount++;
});
print(" End count=" + clientCount);
```
dataCreateDateがないところにlastUpdateDateを入れてみた

#8 mongoDBの開始　終了

```start.sh
$> mongod --dbpath ~/mongoDB_work/data/ --logpath ~/mongoDB_work/mongodb.log
```
```end.sh
$> use admin
$> db.shutdownServer();
$> quit();
```

# 最後に
いろいろMongoDBでやってきたことをメモしました。役に立つと幸いです。