Help us understand the problem. What is going on with this article?

MongoDBクエリなどいろいろ

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

1 データ構造

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

2 挿入 insert

  • 単数の挿入

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

$> db.color.insert(
{
"name" : "black" ,
"nameJpn" : "" ,
"hexCode" : "#000000",
"r" : 0,
"g" : 0,
"b" : 0,
}
)
  • 複数の挿入
$> 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 } )
3回目の測定の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の最後の2つのフラグの意味
最後2番目フラグ・・・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() をつける

$> 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でやってきたことをメモしました。役に立つと幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした