ハンズオンで分かる 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円分とか贈ってもらえると助かります!)