MongoDB基礎

仕事でMongoDBを使うことになりそうなので、調べたことのメモ

MongoDBとは

NoSQLの1つ。
NoSQLの特徴は
- スキーマレス - テーブル結合ができない - トランザクションがない - 水平スケールアウトが用意

RDBとの用語の違い

RDB MongoDB
table collection
row document
column field
index index
primery key _id フィールド

※_idについて _id は自動で生成する主キー。
これはドキュメント登録時に自動で付与される。

操作

データベース

作成

use 名前 でデータベースの作成が行われる。
mongoDBはインストールされている環境でuseを実行して成功すると

switched to db 名前

と出る。

※注意 useを実行した後にデータベースの一覧を出力する show dbs を行っても先程作ったDBは表示されない。
これはuseだけでは実際のデータベースはつくられないため。
useを行った後にコレクションを作成すると実際のデータベースが作られる。

情報の確認

db.stats()を実行するとデータベースの統計情報を確認することができる。

削除

db.dropDatabase() でデータベースの削除が行える。   削除に成功すると { "dropped" : "データベース名", "ok" : 1 } と出力される。

コレクション

作成

db.createCollection("コレクション名") でコレクションを作成できる。
このコマンドを実行すると { "ok" : 1 } と出力される。
これでコレクションが作られる。

一覧表示

コレクション一覧を確認する場合は show collections で確認できる。

情報の確認

db.コレクション名.stats() で出力できる。

削除

db.コレクション名.drop() でコレクションの削除ができる。

補足 コレクションの中のデータを全て消したい場合データ数によっては削除をして、再作成をしたほうば速い。

ドキュメント

登録

  1. 1件だけ登録
    db.コレクション名.insertOne(jsonオブジェクト) を実行することでドキュメントの登録ができる。
    登録されるデータは引数に渡したjsonデータに自動付与される _id がついたデータになる。

  2. 複数件の登録
    db.コレクション名.insertMany([jsonオブジェクト]) で配列に含んでいるオブジェクトを一気に登録してくれる。

※注意
ドキュメントを登録するコレクション名を間違えるとそのコレクションを自動作成をしてドキュメントを登録するので気を名前には気をつける。

検索

  1. 全件検索
    db.コレクション名.find() で登録されている全件を出力してくれる。
    全件取得ではデフォルトで20件ずつしか取得できない。
    ただ、it を続けて入力すると次の20件を取得してくれる。

  2. フィールドを指定して検索
    ここでもfindを利用する。
    全件取得と違いfindの引数を渡すと個別の検索をしてくれる。
    db.コレクション名.find(jsonオブジェクト)
    引数に指定したオブジェクトと一致するドキュメントを出力してくれる。
    また、第2引数にオブジェクトを指定することで取得するフィールドの制限をすることができる。
    {フィールド: 0}で非表示, {フィールド: 1}で表示
    文字列等で部分一致の検索をしたい場合は検索条件のオブジェクトに正規表現を使うと可能
    正規表現なので、前方一致、後方一致など色々と条件を組むことができる。

  3. 配列を検索する
    配列のフィールドを出力する場合は第2引数を指定すればよいが検索をする場合、検索データを配列にすると完全一致での検索になる。 {"name" : "test", "skill" : [ "a", "b", "c" ]} このドキュメント登録があり、skillの一致で検索する場合
    db.test.find({skill:["a", "b", "c"]}) このような検索を組む必要がある。
    一部一致での検索をする場合オブジェクト型の検索データにすれば良い。
    db.test.find({"skill": "a"}) とすることで一部一致の検索が行える。

  4. オブジェクトの検索をする 配列では検索する場合完全一致だったが、オブジェクトの場合一部一致での検索が可能。
    親フィールド.フィールド で検索が行える。
    "name" : "test", "child" : { "a" : 1, "b" : 2 } } このドキュメント登録があり、これを検索する場合
    db.test.find({"child.a": 1}) とすることで検索ができる。
    ちなみにこれは配列のオブジェクトに対しても使える。

  5. 範囲指定 引数のオブジェクトの値としてオブジェクトを指定すると範囲指定での検索ができる
    {$lte: value} $letで以下 {$lt: value} $ltで未満 {$gte: value} $gteで以上 {$gt: value} $gtでより大きい
    noの値が10以下のドキュメントを探す場合 db.test.find({no: {$lte: 10}}) こんな指定になる。
    複合条件で 以上、以下 のような検索を組みたい場合 db.test.find({no: {$lte: 10, $gte: 5}})となる。

  6. and, or {$and: [検索条件]} でand検索ができる。
    orの場合$and -> $or に変えて {$or: [検索条件]} とすればよい。
    例えば no が 0 or 1のデータを検索したい場合
    db.test.find({$or:[{no: 0}, {no:1}]}) このような指定になる。

  7. フィールドの存在での検索 特定のが無いドキュメントを検索する場合
    {フィールド: {$exists: true or false}} で検索ができる
    trueの場合フィールドが存在するもの、falseの場合フィールドが存在しないもの

  8. データ型で検索 $typeを使うことでフィールドの型による検索ができる。
    {フィールド: {$type: "型"}} で検索を型による検索が行える。
    型の指定はダブルクォートで指定をするが、各型に割り振られた番号でも検索は可能。
    ただ、undefinedのしてはできないため存在しないフィールドの検索はこれでは行えない。

  9. 配列の要素数で検索 $sizeを利用
    {フィールド: {$size: 要素数}} で要素数の検索が行える。

  10. 配列の中に指定した値をもつ検索 $allを利用 {フィールド: {$all: [値]}} で指定した値を持つ配列の検索が行える。

ソート

findの結果にsort関数を呼び出すことで検索結果のソートができる。
db.コレクション名.find().sort({ソート条件}) ソート条件にはソート条件にしたいフィールドと1 or -1で指定する。
1は昇順、-1は降順となる。
db.test.find({no: {$exists: true}}).sort({no: 1}) noのフィールドがあるドキュメント取得してnoでソートする場合

件数制限

検索結果が多い場合は結果に件数の制限を指定することができる。
db.コレクション名.find().limit(件数) で引数に指定した件数結果のみに絞ることができる。   ただ、デフォルトの20件制限を変更するものではないため20件を超えて指定した場合次の結果を見るためにはitが必要になる。

開始位置の指定

検索結果の開始指定をずらしたい場合skipを指定する。
db.コレクション名.find().skip(件数) で引数に指定した件数ずらした所から開始する。 limitと組み合わせて、開始番号と終了地点の制御ができる。
組み合わせる場合 db.コレクション名.find().limit(件数).skip(件数) db.コレクション名.find().skip(件数).limit(件数) どちらが先でも問題ない。

更新

登録したドキュメントを更新する。

  1. 1件だけ更新 db.コレクション名.updateOne({検索条件}, {$set: {更新内容}}) で更新ができる。
    検索条件に一致する1件だけ更新がされる。
    あくまで更新なので、フィールド削除は行えない。

  2. 複数件更新 db.コレクション名.updateMany({検索条件}, {$set: {更新内容}}) で複数件の更新ができる。
    updateOneと違い検索条件に一致する全てのドキュメントを更新する。

  3. ドキュメントを差し替える _id以外のフィールドを削除して全部差し替えることができる。
    db.コレクション名.replaceOne({検索条件}, {更新内容})
    updateOneと同様に検索条件に一致する1件だけ更新される。
    replaceManyは存在しない。

  4. 配列に要素を追加する
    updateOneで書いた更新内容を指定する時に "フィールド名.index" と指定することで配列の中身に対して更新を行える。   db.test.updateOne({"skill": "a"}, {$set: {"skill.3": "3"}})

  5. データ登録、または更新 updateOneなどで検索条件に一致しなかった場合に新たにコレクションを追加する指定ができる。
    db.コレクション名.updateOne({検索条件}, {$set: {更新内容}}, {upsert: true})
    第3引数に{upsert: true} と指定すると、update or insertの挙動になる。
    なお、追加されるコレクションは 検索条件と更新内容を合わせた内容になる。

削除

指定したドキュメントを削除する。 1. 1件だけ削除 db.コレクション名.deleteOne({検索条件})
updateOneと同様に検索条件を満たすドキュメントが複数あっても削除されるのは1件のみ

  1. 複数件の削除 db.コレクション名.deleteMany({検索条件})
    検索条件を満たすドキュメントを全て削除する。
    引数に空のオブジェクトを指定した場合コレクション内のドキュメントを全て削除する。

データの集計

findでは集計が行えない。
集計を行う場合はaggregationを利用する
db.コレクション名.aggregate([{$match: {検索条件}, {$group: {集計条件}}}] とかく。
$matchは検索条件、$groupは集計の条件
$matchに関しては省略が可能

{"_id":0,"no":1,"param":0}
{"_id":1,"no":2,"param":0}
{"_id":2,"no":1,"param":1}
{"_id":3,"no":2,"param":1}
{"_id":4,"no":1,"param":2}
{"_id":5,"no":2,"param":2}
{"_id":6,"no":1,"param":3}
{"_id":7,"no":2,"param":3}
{"_id":8,"no":1,"param":4}
{"_id":9,"no":2,"param":4}

このドキュメントが登録されているtestコレクション
noで分類分けしてparamの合計を知りたい場合 db.test.aggregate([{$group:{ _id: "$no", total: {$sum: "$param"}}}])
このようになる。
全件を対象に行うため$matchを省略し、$groupから開始
_id: "$no" でグルーピングし、_idがnoの項目になる新しいドキュメントが作られる。
その後に続くtotal: {$sum: "$param"} でtotalフィールドーがフィールドが$paramの合計値で作られる。

matchを指定する場合

db.test.aggregate([ 
    {$match: {param: {$gt: 2}}},
    {$group:{ _id: "$no", total: {$sum: "$param"}}} 
])

この様にすればよい。

※matchとgroupは順番を逆にすると違う意味になる。 match -> groupと指定をした場合フィルタリングをした結果に集計を行ったが、逆にした場合集計の結果に対してフィルタリングをすることになる。
db.test.aggregate([{$group:{ _id: "$no", total: {$sum: "$param"}}}, {$match: {_id: 1}}])
とした場合結果は { "_id" : 1, "total" : 10 } となる。
aggregateは配列で指定した構文を実行するようなもので match -> group -> matchみたいなこともできる。

その他の集計関数

groupで指定したsumは合計値を出してくれたが、他にも集計の関数はある。
1. $max
最大値
2. $mix 最小値 3. $avg 平均値

$sum を差し替えると利用ができる。

コレクション全体の集計

コレクション全体の集計をしたい場合 _idの値をnullにすれば良い。
db.test.aggregate([{$group:{ _id: null, total: {$sum: "$param"}}}])

カウント

集計の中のデータ個数をカウントする。

noが1のデータの個数を取得
db.test.aggregate([{$match:{no: 1}}, {$count: "count"}])

testコレクションに登録されているドキュメント数を取得
db.test.aggregate([{$count: "test_count"}])

グループ毎のカウント
$countは配列のlengthを取得するようなものなので、グループ毎のカウントを取得することはできない。
1クエリでグループ毎にカウントをする場合$sum を使うのがよさそう。

db.test.aggregate([
{$group:{_id: "$no", count: {$sum: 1}}}
])

これでnoのフィールド毎のカウントを集計することでグループ毎のカウントを計算できる。

コレクションの結合

$lookup を利用しコレクションを結合することができる。 結合する条件は2つのコレクション間で一致するフィールドを持つ必要がある。

{"_id":0,"name":"taro"}
{"_id":1,"name":"jiro"}

このドキュメントが登録されているtest2コレクションを追加してやった場合testコレクションと_idで一致できるため結合ができる。

$lookup

db.結合元コレクション.aggregate(
{
    $lookup: {
        from: "結合先コレクション",
        localField: "結合元コレクション",
        foreignField: "結合先コレクション",
        as: "結合したフィールド名"
    }
});

となっている。

先程のtest2コレクションとtestコレクションを_idが同じデータで結合する場合

db.test2.aggregate([
{
    $lookup: {
        from: "test",
        localField: "_id",
        foreignField: "_id",
        as: "comb"
    }
}
])

となり、結果は

{ 
    "_id" : NumberInt(0), 
    "name" : "taro", 
    "comb" : [
        {
            "_id" : NumberInt(0), 
            "no" : NumberInt(1), 
            "param" : NumberInt(0)
        }
    ]
}
{ 
    "_id" : NumberInt(1), 
    "name" : "jiro", 
    "comb" : [
        {
            "_id" : NumberInt(1), 
            "no" : NumberInt(2), 
            "param" : NumberInt(0)
        }
    ]
}

こうなる。

結合するのを一部に絞りたい場合 $project を利用する。

db.test2.aggregate([
{
    $lookup: {
        from: "test",
        localField: "_id",
        foreignField: "_id",
        as: "comb"
    }
},
{
    $project: {
        "comb._id": 0
    }
}
])

$projectを実行するタイミングは結合後のデータが対象になるので、結合先の_idがいらない場合 comb._id この様にフィールド名が必要となる。

設定

インデックス

インデックスを利用すると検索を早くしてくれる。
ほぼ必須の設定
シャーディングのことも考えて複合インデックスで作るのがよさそう。

作成

db.コレクション名.createIndex(
    { キー: 1, キー: -1 },
    {オプション}
)

1を指定すると昇順でインデックスを作成
-1を指定すると降順でインデックスを作成

オプション | background | インデックスの作成をバックグラウンドで行うデフォルトfalse | | ----------------------- | ------------------------------------------ | | name | インデックス名の指定 | | unique | インデックスをユニークにする。ユニークの場合nullは扱えない デフォルトfalse | | partialFilterExpression | フィルタの設定をして、一致したドキュメントのみインデックスを作成する。 |

確認

db.コレクション名.getIndexes()

db.test.createIndex({group: 1}) を行った testコレクションに対して実行すると

[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "luckyfarmer_log.test"
    },
    {
        "v" : 2,
        "key" : {
            "group" : 1
        },
        "name" : "group_1",
        "ns" : "luckyfarmer_log.test"
    }
]

このような結果になる。
1件目は_idフィールドに対してのインデックスでこれはデフォルトで作成される。
2件目はcreateIndexで作った新たなインデックスの設定

削除

db.コレクション名.dropIndex(インデックス名);

で削除が行える。
引数のインデックス名は getIndexes の結果にある nameフィールドの値

再構築

インデックスは再構築をすることができる。
目的としてはデータ量のダイエットのために行われる。

データを入れていくとどんどんインデックスのデータが膨らんでムダなデータができてくる。
そのムダなデータを落として、ディスクの使用量を削減する。

db.コレクション名.reIndex()

再構築を行うとデフォルトインデックスの再構築がまず行われ、その間データベースにロックがかかる。
それ以外のインデックスはバックグラウンドで再構築される。

種類

インデックスにはいくつか種類がある。

  • デフォルトインデックス
    _id フィールドに自動的に作られる。削除はできない。
  • テキストインデックス
    文字列に対して検索クエリをサポートする。 作成する場合 インデックス作成時の値に"text" を指定
  • TTLインデックス
    特定の時間にコレクションからドキュメントを削除するために利用
    createIndexの引数にcreateIndex({キー}, {expireAfterSeconds: 秒数}) で指定をする。
    指定したフィールドの値にはDate型、Date型を含む配列でないと動作しない。
    因みに指定は秒だが、チェックは1分間隔らしい