ハンズオンで分かる MongoDB チュートリアル
前回(と言ってももう2ヶ月前か……1.5.8まででちゃってますね。(追記:丁度今日、1.6.0 安定版がリリースされました!)例によってビルドはクソ長いので注意して下さい)、「Mac OS X で MongoDB を動かす」で、Mac OS X での MongoDB インストール、起動、停止、デーモン化をしました。
今回は、付属のシェルで簡単なCRUD操作をしてみたいと思います。なので、OSやプログラミング言語は関係ありません。素の MongoDB を学ぶ目的で作成しました。SQL 知らないと ORM が満足に使えないように、今回の CRUD 操作を知っておけば、各言語用の ORM で悩むことが少なくなると思います。
なお、今回もいちいちイラッ☆とくる語りが入ります。
復習しておきましょうか、MongoDB っていうのは、
- ドキュメント指向ストレージで
- インデックスをサポートしていて
- レプリケーションができて高可用性に富んでいて
- 自動データ分割してくれて
- 多彩な検索クエリが使えて
- 更新がアトミックで速くて
- Map/Reduce も使えたりしちゃったりして
- GridFS っていう何でもつっこめるファイルシステムを使っていて
- 商用サポートもある
といった特徴を持つNoSQLデータベースです。個人的に、Rails 3 との相性は抜群だと思いますね。
そして、MongoDB での用語を説明しておきます。
RDBMS | MongoDB |
---|---|
データベース | データベース |
テーブル | コレクション |
レコード | ドキュメント |
データベースはコレクションを含み、コレクションはドキュメントを含むという関係です。
なんで用語を変えたのでしょうか? それはドキュメント指向データベースだからです! ドキュメントっつーのはアレだ、カラムとか気にしないで何でもぶっこめるっつーこった。すばらしい!が、ちょっと不安ですね〜。アレがないコレがないとかありそうですねー。まー、そこはアプリで頑張るっつーことで、次いきましょう!
では、シェルを起動します。
$ mongo MongoDB shell version: 1.4.0 url: test connecting to: test type "help" for help >
ほい、シェルが立ち上がりましたねー。引数省略したので、localhost の test データベースにつながったようです。
ちなみに、正式に書くと、
$ mongo localhost:27017/test
となります。外のサーバに接続するときはこの形式でつないでください。
じゃあ、ちょっくら何が入っているか覗いてみましょうか。show dbs コマンドでデータベース一覧を出します。
> show dbs admin local
へー、admin と local ってデータベースがあるんすねー。これは管理用っぽいから今は触らないようにしておきましょうねー。私は覗いてびっくりしましたが、皆さんも何が起きるかドキドキしながら覗くといいですよー。
つーか、test データベースにつながってるって言ったのに、testデータベースないじゃん!と気づいたあなた!お目が高い!そうなんですよー、どうやら、MongoDB はドキュメントの作成時にコレクションもデータベースも作成するっぽいんですよー。LazyLoadっぽいなー。
データベースを変更してみましょう。use を使います。
> use mysql
switched to db mysql
はい、mysql データベースに変更しました。え?ややこしい?気にすんな!
じゃあ、どんなテーブルコレクションが入ってるか調べてみましょう。show collections コマンドを使います。
> show collections >
はい、空でした! 当たり前っすね。じゃあ、これから CRUD を説明して行きます。意外と長丁場ですよ、覚悟して下さい!
Create
ドキュメントの作成には insert() メソッドを使います。
> db.things.insert({x: 1})
はい、これで things テーブルコレクションに x が 1 というレコードドキュメントが作成できましたよー。del タグそろそろうざいっすかね、サーセンフヒヒ
あ、コレクション名を thing じゃなくて things にしたのは、Rails風です。
コレクションが出来ているかどうかだけ確認しておきましょうか。
> show collections
things
system.indexes
おお、インデックスも作られている!インデックスサポートはマジだったか。
やはりここは、ドキュメント指向っぽいとこを見ておきましょうか。
> db.things.insert({y: "string"})
MongoDB△ なんでも入る!ガバマンっすね!……ただいま不適切な表現があったことをおわびします……
ところがこれだけじゃないんです!
> db.things.insert({x: 2, a: ["apple", "banana"]}) > db.things.insert({x: 3, b: {p: "hoge", q: "fuga"}})
中に配列やハッシュをぶち込めるんです!かっけー。このドキュメントは配列やハッシュが「エンベッド」されてるといいます。
あ、MongoDB の♪もっといいとこ見てみたい♪そぉれ、いっき♪いっき♪いっき♪いっき♪……
> for (var i = 0; i < 10; i++) db.things.insert({n: i, m: 5});
うぇ……このシェル JavaScript が使えやがる……恐ろしいな……
Retrieve
おまたせ!やっとコレクションの中身が見れますよ! ちなみに CRUD の R を Read とせずに Retrieve としたのは、MongoDB の力から言うとこちらの方が相応しいかなと思ってのことです。KVS とは違うのだよ KVS とは!
じゃあ、早速。find() メソッドで検索します。
> db.things.find() { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
すげぇ、そのまま飲み込んでるよ僕のエクスカリバー……
_id ってのは、auto increment 列みたいなもので、これがまさにアイデンティティになるわけですね。NoSQL の一種ですからね。これが主キーです。
じゃあ、今のクエリの変種をば、カーソルを使います。
> var cursor = db.things.find() > while (cursor.hasNext()) printjson(cursor.next()); { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
さっきの find() だけでドキュメントが表示されてたのは、シェルがいい具合に評価してくれてたみたいですね。find() で帰ってくるのはカーソルオブジェクトですね。
printjson() メソッドで表示してますが、これは BSON 使ってるからですかね。BSON というのはですね、Binary JSON の略でして、MongoDB はバイナリ形式の JSON でデータを持ってるんですね。
forEach() メソッド使って、こんなんもできますよー、
> db.things.find().forEach(printjson) (略)
カーソルって使いでがありますねー、[] もサポートしてるんですね。
> var cursor = db.things.find(); > printjson(cursor[4]); { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
もできます。
あと似たようなので、こんなんも。toArray() メソッドを使っています。
> var arr = db.things.find().toArray(); > arr[4] { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
JavaScript シェルが自由すぎる……
シェルの多機能さはこれぐらいにして、ORMで発行されそうなクエリをやっていきますよー。
SELECT * FROM things WHERE y="string"
SQLで言うとこんな感じのクエリは、
> db.things.find({y: "string"}) { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
こうです。簡単ですねー。
じゃあ、SQLでいうカラム指定の場合
SELECT n FROM things WHERE m=5
は、
> db.things.find({m: 5}, {n: true}) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9 }
なるほどなるほど、
db.collections.find([query[, cols]])
って感じの関数なんですねー。
一つだけ引きたい場合はどうするんでしょ?
SELECT * FROM things WHERE m=5 LIMIT 1;
的な。
> db.things.findOne({m: 5}) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 }
できますねー。
エンベッドされた値で検索できるのですかね?
まずは配列でやってみましょう。
> db.things.find({a: ["apple", "banana"]}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } > db.things.find({a: ["apple"]}) (ドキュメントなし) > db.things.find({a: "apple"}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
できますねー。
次はハッシュ。
> db.foo.find({b: {p: "hoge", q: "fuga"}}) { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } > db.foo.find({b: {p: "hoge"}}) (ドキュメントなし) > db.foo.find({"b.p": "hoge"}) { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
なるほどねー。最後の "b.p" みたいなのは、ドットノーテーションと言います。
それではここからは SQL と対応するクエリを駆け足でやっていきましょう。
AND
SELECT * FROM things WHERE n=4 AND m=5;
> db.things.find({n: 4, m: 5}) { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 }
OR → $or
※ これは MongoDB 1.5.3 からの機能となります。
SELECT * FROM things WHERE x=2 OR n=2;
> db.things.find({$or: [{ x: 1 }, {n: 2}]}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
<, <=, >, >= → $lt, $lte, $gt, $gte
SELECT * FROM things WHERE n<2; SELECT * FROM things WHERE n<=2; SELECT * FROM things WHERE n>7; SELECT * FROM things WHERE n>=7;
> db.things.find({n: {$lt: 2}}) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } > db.things.find({n: {$lte: 2}}) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } > db.things.find({n: {$gt: 7}}) { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 } > db.things.find({n: {$gte: 7}}) { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
<>, != → $ne
SELECT * FROM things WHERE m <> 5;
> db.things.find({m: {$ne: 5}}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
IN → $in
SELECT * FROM things WHERE n IN (2, 4, 6);
> db.things.find({n: {$in: [2, 4, 6]}}) { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 }
NOT IN → $nin
SELECT * FROM things WHERE n NOT IN (2, 4, 6);
> db.things.find({n: {$nin: [2, 4, 6]}}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
IS NULL, IS NOT NULL → $exists
SELECT * FROM WHERE x IS NOT NULL; SELECT * FROM WHERE n IS NULL;
> db.things.find({x: {$exists: true}}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } > db.things.find({n: {$exists: false}}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } }
LIMIT → limit()
SELECT * FROM things LIMIT 3;
> db.things.find().limit(3) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] }
OFFSET → skip()
SELECT * FROM things OFFSET 4 LIMIT 3;
> db.things.find().skip(4).limit(3) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 }
ORDER BY → sort(), $slice
SELECT * FROM things ORDER BY n LIMIT 2; SELECT * FROM things ORDER BY n DESC LIMIT 2; SELECT * FROM things ORDER BY n OFFSET 3 LIMIT 2; SELECT * FROM things ORDER BY n DESC OFFSET 3 LIMIT 2;
> db.things.find().limit(2).sort({n: 1}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } > db.things.find().limit(2).sort({n: -1}) { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } > db.things.find().skip(3).limit(2).sort({n: 1}) { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } > db.things.find().skip(3).limit(2).sort({n: -1}) { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 }
※ $slice は MongoDB 1.5.1 からの機能になります。
> db.things.find({}, {n: {$slice: 2}}) > db.things.find({}, {n: {$slice: -2}}) > db.things.find({}, {n: {$slice: [3, 2]}}) > db.things.find({}, {n: {$slice: [-3, 2]}})
COUNT → count()
SELECT COUNT(*) FROM things;
> db.things.find().count() 14
ここから先は MongoDB に特有の機能を紹介します。
$where
JavaScript を評価して検索します。
> db.things.find({$where: "this.n > 7"}) { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 } > db.things.find("this.n > 7") { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 } > f = function() { return this.n > 7; }; db.things.find(f) { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "m" : 5, "n" : 8 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "m" : 5, "n" : 9 }
$mod
字面の通りモジュロ(割り算の余り)ですねー。
> db.things.find({n: {$mod: [3, 0]}}) { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 }
$all
$in に似ていますが、エンベッドされた配列に対するもので、さらに、指定した配列の全ての値を含まなければなりません。
> db.things.find({a: {$all: ["apple", "banana"]}}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } > db.things.find({a: {$all: ["apple"]}}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } > db.things.find({a: {$all: ["apple", "nothing"]}}) (ドキュメントなし)
$size
エンベッドされた配列のサイズで検索します。
> db.things.find({a: {$size: 2}}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } > db.things.find({a: {$size: 1}}) (ドキュメントなし)
$type
BSON型の型番号で検索します。
型と型番号の対応は以下のようになります。
型名 | 型番号 |
---|---|
Double | 1 |
String | 2 |
Object | 3 |
Array | 4 |
Binary data | 5 |
Object id | 7 |
Boolean | 8 |
Date | 9 |
Null | 10 |
Regular expression | 11 |
JavaScript code | 13 |
Symbol | 14 |
JavaScript code with scope | 15 |
32-bit integer | 16 |
Timestamp | 17 |
64-bit integer | 18 |
Min key | 255 |
Max key | 127 |
> db.foo.find({y: {$type: 2}}) { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } > db.foo.find({x: {$type: 2}}) (ドキュメントなし)
正規表現
なんと!正規表現でも検索できます!
> db.things.find({y: /^str/}) { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } > db.things.find({y: /^str.*/}) { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } > db.things.find({y: /^str.*$/}) { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" }
なお、上記3つの例は全てインデックスを使っているそうですが、後者の2つは全ドキュメント検索するのでスピードが遅いそうです。
$elemMatch
エンベッドされた配列の中を検索します。
> db.things.insert({x: [{a: 1, b: 3}, 7, {b: 99}, {a: 11}]}) > db.things.find({x: {$elemMatch: {a: 1, b: {$gt: 1}}}}) { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] } // ドットノーテーションでも同じことができます。 > db.things.find({"x.a": 1, "x.b": {$gt: 1}}) { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] }
$not
否定を表すメタ演算子です。他の演算子と組み合わせて使います。
> db.things.find({n: {$not: {$mod: [2, 0]}}}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 } { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] }
ふー、疲れた。これで Retrieve は一通り終わりです。group() もあるんですが、Map/Reduce が絡むので、また後日。
なんかこの辺でテンション下がってきました。平常運転でやろうかなと思います。
Update
更新がまた多彩なんですよねー。よっし、がんばるぞぅ、おう。みんなも頑張って読んで!
save()
最後に作成したドキュメントがややこしすぎるので、もっと簡素な奴に更新してみましょうか。
> var doc = db.things.find().toArray()[14] { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ] } > doc.x = {a: 1, b: 2, c: 3} { "a" : 1, "b" : 2, "c" : 3 } > db.foo.save(doc) > doc = db.things.find().toArray()[14] { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 3 } }
はい、更新できました。簡単すね。
もちろん JavaScript 風に値をいじれます。
> doc.x.c++ 3 > db.things.save(doc) > doc = db.things.find().toArray()[14] { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } }
ORM 触ってるのと、あんまかわんないような気がしますね。
update()
db.collection.update(criteria, objNew[, upsert[, multi]])
引数を解説すると、
- criteria - 検索条件
- objNew - 更新オブジェクトまたは修飾子
- upsert - update + insert の合成語。ドキュメントがあれば更新し、なければ新規に作成します。
- multi - 検索条件に複数該当する場合に全部更新するかどうか決めます。デフォルトは最初の1レコードのみです。
> db.things.find({x: 1}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 1 } > db.things.update({x: 1}, {x: 255}) > db.things.find({x: 1}) (ドキュメントなし) > db.things.find({x: 255}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 255 }
できました。でも、objNew と丸ごと入れ替わっちゃうので、複雑なオブジェクトなんかの場合、使いでが悪いです。
次から説明する、修飾子と一緒に使えば、そこらへん楽になります。
$inc
値をインクリメントします。
> db.things.update({x: 255}, {$inc: {x: 1}}) > db.things.find({x: 255}) (ドキュメントなし) > db.things.find({x: 256}) { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 256 }
save() の例でインクリメントしましたが、こちらはアトミックに更新することができます。分散環境を考えると便利ですね。
$set
update() を普通に使うとobjNewに破壊的に更新されてしまいますが、この $set を使うことで、差分更新ができます。
> db.things.find({n: 9}) { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 5 } > db.things.update({n: 9}, {$set: {m: 10}}) > db.things.find({n: 9}) { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9, "m" : 10 }
$unset
フィールド自体を削除します。
> db.things.update({n: 9}, {$unset: {m: 1}}) > db.things.find({n: 9}) { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9 }
$push
配列フィールドに値を追加します。
> db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "x" : 2, "a" : [ "apple", "banana" ] } > db.things.update({x: 2}, {$push: {a: "peach"}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach" ], "x" : 2 }
$pushAll
配列フィールドに配列を追加します。
> db.things.update({x: 2}, {$pushAll: {a: ["orange", "grape"]}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 }
$addToSet
ワンモアセッ!……すいません、つい古き良きビリーの声が聞こえてきました。この記事書くために寝てないんすよ。許して♡
配列フィールドに値を追加するのは $push と同じなんですが、重複になるときは追加しません。
> db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 } > db.things.update({x: 2}, {$addToSet: {a: "grape"}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 } > db.things.update({x: 2}, {$addToSet: {a: "strawberry"}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape", "strawberry" ], "x" : 2 }
$pop
配列フィールドの末尾を削除します。-1を指定した場合は、先頭が削除されます。
> db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape", "strawberry" ], "x" : 2 } > db.things.update({x: 2}, {$pop: {a: 1}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "apple", "banana", "peach", "orange", "grape" ], "x" : 2 } > db.things.update({x: 2}, {$pop: {a: -1}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "orange", "grape" ], "x" : 2 }
$pull
配列フィールドから、指定した値を削除します。
> db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "orange", "grape" ], "x" : 2 } > db.things.update({x: 2}, {$pull: {a: "orange"}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "grape" ], "x" : 2 }
$pullAll
$pushAll の逆ですね。配列フィールドから指定した配列の値を全部削除します。
> db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana", "peach", "grape" ], "x" : 2 } > db.things.update({x: 2}, {$pullAll: {a: ["peach", "grape"]}}) > db.things.find({x: 2}) { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 }
$
ちょっとややこしいのですが、ポジショナルオペレーターというらしいです。検索条件でマッチした配列の要素を表します。
> db.things.insert({title: "foo", comments: [{by: "joe", votes: 3}, {by: "jane", votes: 7}]}) > db.things.find({"comments.by": "joe"}) { "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] } > db.things.update({"comments.by": "joe"}, {$inc: {"comments.$.votes": 1}}) > db.things.find({"comments.by": "joe"}) { "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
なんか使いどころが難しそうですね。
以上で Update は終わりです!
Delete
いろいろ削除する前に foo コレクション全体が現状どうなっているか確認してみましょう。
> db.things.find() { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5c"), "n" : 5, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5d"), "n" : 6, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5e"), "n" : 7, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5f"), "n" : 8, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa60"), "n" : 9} { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 } { "_id" : ObjectId("4c598d011546827805e1aa53"), "x" : 256 } { "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
いっぱい作りましたねー。
じゃあ、まずは、x = 256 のレコードを削除してみたいと思います。
> db.things.remove({x: 256}) > db.things.find({x: 256}) (ドキュメントなし)
削除できました。
では、次に、n > 4 のドキュメントを削除してみます。
> db.things.remove({n: {$gt: 4}, $atomic: true}) > db.things.find() { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 } { "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
綺麗に削除できましたね。$atomic 修飾子は、その名の通りアトミックに複数レコードを削除します。自動分散機構があるもんで、気を使ってますね。
オブジェクトが得られているときには、こうやって削除します。
> doc = db.things.findOne({x: 3}) { "_id" : ObjectId("4c5991191546827805e1aa56"), "x" : 3, "b" : { "p" : "hoge", "q" : "fuga" } } > db.things.remove({_id: doc._id}) > db.things.find() { "_id" : ObjectId("4c5991b41546827805e1aa57"), "n" : 0, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa58"), "n" : 1, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa59"), "n" : 2, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5a"), "n" : 3, "m" : 5 } { "_id" : ObjectId("4c5991b41546827805e1aa5b"), "n" : 4, "m" : 5 } { "_id" : ObjectId("4c5a0f602e6af24434f87cfe"), "x" : { "a" : 1, "b" : 2, "c" : 4 } } { "_id" : ObjectId("4c598e261546827805e1aa54"), "y" : "string" } { "_id" : ObjectId("4c5991101546827805e1aa55"), "a" : [ "banana" ], "x" : 2 } { "_id" : ObjectId("4c5a37442e6af24434f87d00"), "title" : "foo", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" :
_id 指定ですね。
では最後に、綺麗サッパリ、全ドキュメントを削除します。
> db.things.remove({})
> db.things.find()
(ドキュメントなし)
はい、これで CRUD は終わりです。
後片付け
things コレクションを削除しましょう。
> show collections things system.indexes > db.things.drop() true > show collections system.indexes
データベースも削除しましょう。
> db.dropDatabase() { "dropped" : "mysql.$cmd", "ok" : 1 } > show dbs admin local
はい、これで終了です!お疲れ様でしたー。
(アサマシ貼りたいけど、MongoDBの本、洋書でさえまだ出てないw)
(あ、今クソ貧乏なんで、この記事に感動した方は babie.tanaka@gmail.com 宛に Amazon ギフト券500円分とか贈ってもらえると助かります!)