Skip to content

Instantly share code, notes, and snippets.

@ykst
Last active February 1, 2024 10:02
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ykst/d1a024f1d800311744be to your computer and use it in GitHub Desktop.
Save ykst/d1a024f1d800311744be to your computer and use it in GitHub Desktop.
逆引きmongodb

逆引きmongodb

バージョン3以降対象

環境系

認証

use <db_name>
db.auth("<user name>", "<password")

参照系

コレクションの中身の一覧

db.<collection name>.find()

インデックスの一覧

db.<collection name>.getIndexes()

コレクションの一覧

db.getCollectionNames()

ドキュメントの検索

db.foo.insert([{x:1}, {x:2}]) // {x:1}と{x:2}のドキュメントをfooに追加
db.foo.find({x:1}) // x == 1のドキュメントを検索

db.foo.insert({y:{z:1}}) // 内部ドキュメントのあるドキュメントを追加
db.foo.find({y: {z: 1}}) // y.z == 1のドキュメントを検索

検索条件の値としてリテラルを使うと一致検索になり、他にもクエリオプションを指定することができる。

db.foo.find({x: {$gt: 5}}) // x > 5となる全てのドキュメントを検索
db.foo.find({x: {$ne: 5}}) // x != 5となる全てのドキュメントを検索

SQLのselectのように、検索結果をフィルターするにはfindの第二引数でon/offの指定を行う。_idは明示的に0を指定しないと消せない。

db.foo.find({x: {$ne: 5}}, {x:1, _id:0}) // 1:on, 0:off. 

あるフィールドが存在するドキュメントを検索する

$exists:<boolean>を使う。存在しないことを条件にする場合はfalseを指定する

db.foo.find({a: { $exists: true}})

あるフィールドがx以上y以下の値の範囲にあるドキュメントを検索する

db.foo.find({a: {$gte: x, $lte: y}})

配列フィールドの要素数を指定して取り出す

配列に対するフィルタ条件に$sliceを指定することで、取り出す数を絞り込める

db.foo.insert({i:0, a:[1,2,3,4,5]})
db.foo.find({i:0}, {a: {$slice: 3}})  // [1,2,3]
db.foo.find({i:0}, {a: {$slice: -3}})  // [3,4,5]

削除系

コレクションの中身を全削除

db.<collection name>.remove({})

※ ドキュメントを全て消してもインデックスは残り続けるので、スキーマを変えて再投入した場合はnull値の重複としてユニーク制約に違反することがある。

コレクションを削除

db.<collection name>.drop()

※ dropではインデックスも全て消去される

インデックスの個別消去

getIndexesなどで見られる"name"を指定して個別のインデックスを消去する

db.foo.dropIndex("<index name>")

インデックスの全消去

db.<collection name>.dropIndexes()

更新系

upsert

db.foo.update({i:0}, {$set: {x:1}}, {upsert:true})

複数ドキュメントが見つかった場合は最初の一つがupdateされる。

検索に一致する全てのドキュメントを更新

デフォルトではマッチしたドキュメントのうち一つだけしかupdateできないので、{multi:true}を指定する。

db.foo.update({i:0}, {$set: {x:1}}, {multi:true})

$を使わない単純な入れ替え指定はmultiでは使えない

数値フィールドをインクリメントする

db.foo.update({i:0}, {$inc: {x:1}}) // x = x + 1
db.foo.update({i:0}, {$inc: {x:1, y:-1}}) // x = x + 1, y = y - 1

加算する値は任意の数値が使用可能。$inc対象フィールドが存在しなかった場合は0を初期値として計算した新しいフィールドが挿入される。

フィールドを削除する

ドキュメント内部のフィールド削除は$unsetを使用する。void 0null値をセットした場合はnullが格納されてしまう。

db.foo.update({i:0}, {$unset: {x:""}}) // xを削除。""のところは何でもいい

条件に一致するドキュメントの配列に要素を追加する

db.foo.insert(i:0, {a:[1,2,3,4,5]})
db.foo.update({i:0}, {$push:{a:6}})

条件に一致するドキュメントの配列に 重複しない 要素を追加する

mongodbのunique indexはドキュメント内部での唯一性を保証しない。このため、配列要素の重複登録を防ぐには次のようにする。

db.foo.insert(i:0, {a:[1,2,3,4,5]})
db.foo.update({i:0, a:{$ne:5}}, {$push:{a:5}}) // 検索に失敗するので更新はされない

ただし、この方法ではドキュメント配列の場合に複雑な検索条件を書く必要が生じる。 別の方法としては、$addToSetを使用してSetとして要素を登録するというものがある。

db.foo.insert(i:0, {a:[1,2,3,4,5]})
db.foo.update({i:0}, {$addToSet:{a:5}}) 

ここまでのいずれの方法も�unique制約違反とは異なりエラーを出さないので注意が必要。

内部配列を検索し、マッチしたものを更新する

クエリは内部ドキュメントと同じ方式で出来る。見つかった要素を更新する場合は$で参照する

db.foo.insert({a:[1,2,3,4]})
db.foo.update({a:1}, {$set: {'a.$': 100}}) // [100,2,3,4]

db.foo.insert({x:[{i:1},{i:2},{i:3},{i:4}]})
db.foo.update({'x.i':1}, {$set: {'x.$.i': 100}}) // [{i:100},{i:2},{i:3},{i:4}]

内部配列に要素を追加すると同時にソートする

$push$each$sortを組み合わせる。ドキュメント配列とそれ以外で少し指定の仕方が異なる。

db.foo.insert({i:0, a:[1,2,3,4]})
db.foo.update({i:0}, {$push: {a: {$each: [2], $sort: 1}}}) // $sortで昇順を指定[1,2,2,3,4]

db.bar.insert({i:0, a:[ {x:1},{x:2},{x:3},{x:4}]})
db.bar.update({i:0}, {$push: {a: {$each: [{x:2}], $sort: {x:1}}} }) // [{"x":1},{"x":2},{"x":2},{"x":3},{"x":4}]

条件にマッチしなかったらinsertする

upsert$setOnInsertを組み合わせる

db.foo.findAndModify({query: {i:0}, update: {$setOnInsert: {i: 0}}, upsert: true})

更新した後の値を取得する

デフォルトの挙動では更新前の値が返り、クエリしたドキュメントが無かった場合はnullが返って来る。 クエリオプションでnewをtrueにすることで、更新後の値をとることができる。

db.foo.findAndModify({query: {i:0}, update: {$setOnInsert: {i: 0}}, upsert: true, new: true})

集計系

Group By + Count

barというフィールドが一致するグループの合計を取る

db.foo.aggregate({$group:{_id: "$bar", count: {$sum:1}}})

barbuzzが一致するグループの合計を取る

db.foo.aggregate({$group:{_id: {bar: "$bar", buzz: "$buzz"}, count: {$sum:1}}})

条件を指定してGroup By

barの値がtrueに等しいレコードで、buzzによるGroup By + Count。

 db.foo.aggregate({$match: {"bar": true}}, {$project: {buzz: 1}} , {$group:{_id: "$buzz", count: {$sum:1}}})

チューニング系

インデックスの作成

db.foo.insert({ a: 1, b:2, c: {d: "a"} })
db.foo.ensureIndex({i:1})  // 単一�インデックス
db.foo.ensureIndex({i:1, b:1}) // 複合インデックス
db.foo.ensureIndex({i:1, 'c.d':-1}) // 内部ドキュメントのフィールドは.区切りで指定
db.foo.ensureIndex({c:1}) // 内部ドキュメントそのものを指定するとその"全てのフィールドの組み合わせ"がインデックスされる

複合インデックスの1-1はソートキーをインデックスする際の昇順・降順の指定となる。

for (var i = 0; i < 10000; ++i) { db.foo.insert({ i: i, j: -i}) }
db.foo.ensureIndex({i:1, j:-1})
db.foo.find().sort({i:1, j:-1}) // インデックス通りのソート
db.foo.find().sort({i:1, j:1}) // インデックスされた順番ではないため、FETCHによる外部ソートが発生する

配列フィールドに対するインデックスは内部ドキュメントのインデックスと同様に貼ることが出来る。

db.bar.insert({ i: 1, array:[1,2,3,4,5]})
db.bar.ensureIndex({array:1})
db.bar.find({array: 2}) // array要素に2を含むものを検索
db.bar.insert({ i: 1, array:[1,2,3,4,5]})
db.bar.ensureIndex({array:1})
db.bar.find({array: 2}) // arrayに対するindexが効いている

また、複合インデックスは先頭からマッチするフィールドについては単一インデックスとしての役割も兼ねる。これをindex prefixと言う。

db.foo.ensureInsex({a:1, b:1})
db.foo.find({a:0, b:1}) // 複合インデックスによるスキャン
db.foo.find({a:0}) // インデックスプリフィックスによるスキャン
db.foo.find({b:0}) // インデックスを使用しないスキャン

このため、次のようなprefixが被るようなインデックスを設定する必要性はない

db.foo.ensureInsex({a:1, b:1})
db.foo.ensureInsex({a:1}) // 既に上のインデックスでaはインデックス対象になっているため、冗長

参考: http://docs.mongodb.org/manual/core/index-compound/#compound-index-prefix

unique制約を張る

index作成のオプションに{unique: true}を付ける。

db.foo.ensureIndex({i:1}, {unique:true})
db.foo.insert({i:0})
db.foo.insert({i:0}) // エラー

arrayに対するunique制約は、コレクション中の全ての配列要素でunique制約が満たされている必要がある。

db.foo.ensureIndex({a:1}, {unique:true})
db.foo.insert(a:[1,2,3])
db.foo.insert(a:[2]) // 要素が被っているのでエラー
db.foo.insert(a:[4,4]) // ただし、insertの時点で被っているのは許される(!)

落とし穴

  • mongodbではインデックスされているフィールドがinsert時に指定されなかった場合はnull値を挿入する。 この結果、unique制約がnullに適用されることになるため、続けてフィールドを省略すると重複レコードとしてエラーが発生する。
  • コレクション内でunique制約に矛盾が発生している状態で新たにensureIndexでunique制約を追加しようとするとエラーが発生する

explain

db.<collection>.<query>.explain("executionStats")

IXSCANが現れていればインデックスが効いている。COLLSCANが見えたらO(N)検索になっているのでヤバい。

inputStageindexNameで使用されたインデックスを調べられる。

totalDocsExaminedで検索したドキュメント数が表示される。

参考: http://docs.mongodb.org/manual/reference/explain-results/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment