Morning Girl

Web API, Windows, C#, .NET, Dynamics 365/CRM etc..

Windows版MongoDBのインストール・MongoShellを通してCRUDコマンドを打ってみる

というわけで前回に引き続き、MongoDBのインストールと、一通りのCRUD操作をMongoShellから打ってみたいと思います。

インストールしたのは、MongoDB 3.6.1のWindows版です。

下の公式マニュアルを参考にすれば、そこまで難しくなくインストールすることができます。

Install MongoDB Community Edition on Windows — MongoDB Manual 3.6

Windows版MongoDBのインストール

今回使用するのは、Community Editionです。

以下のURLのMongoDB Download Centerにアクセスして、

MongoDB Download Center | MongoDB

[Community Ediotn]→[Windows

好きなバージョンを選択して、と言いたいところですが、私が見たときには[Windows Server 2008 R2 64-bit and later, with SSL support x64]一択でした。

一瞬Windows Server 2008 R2!?と躊躇してしまいますが、and laterと書いてあるので、Windows10とかでも大丈夫です。

f:id:sugimomoto:20180104132642p:plain

ダウンロードが完了したら、後はWizardに沿って進めていくだけで、基本的にインストールすることが可能です。

f:id:sugimomoto:20180104132652p:plain

f:id:sugimomoto:20180104132658p:plain

とりあえず今回は、Completeでインストールします。

f:id:sugimomoto:20180104132715p:plain

ここでは、MongoDBをGUIで操作するためのツールをインストールするかどうか聞かれます。

結構便利なので、私としてはインストールしておくことをオススメします。

f:id:sugimomoto:20180104132720p:plain

あとは[Install]ボタンをクリックすれば、OKです。

f:id:sugimomoto:20180104132724p:plain

Install 完了しました。

f:id:sugimomoto:20180104132729p:plain

環境変数への登録

今後、CommandからMongoDBの起動を行ったりするので、インストールフォルダへPathを通しておくのがおすすめです。

MongoDBデフォルトのインストールフォルダは[C:\Program Files\MongoDB]です。

実行プログラムは[C:\Program Files\MongoDB\Server\(version)\bin]にあるので、このファイルパスをPath環境変数に追加すればOKです。

f:id:sugimomoto:20180104132735p:plain

MongoDB用ディレクトリの作成と起動

それでは実際にMongoDBを起動してみたいと思います。

コマンドプロンプトを立ち上げて、まずMongoDBのデータ格納用ディレクトリの作成しておきます。

md \MongoDB\Test

作成したフォルダパスを引数につけて、MongoDBのデータベースを起動します。先程Pathを通しているので、[mongob]コマンドと先程フォルダパスを引数にして、以下の通りコマンドを打ちます。

mongod --dbpath c:\MongoDB\Test

上記コマンドでMongoDBのServerが起動され、色々とメッセージが表示されていきます。最終的に、以下の通り、メッセージが表示されればOKです。デフォルトポートは27017で、これを経由して、ShellやCompassなどのツールからアクセスします。

>waiting for connections on port 27017

Mongo Shellを使って、MongoDBへ接続

MongoDBを起動したコマンドプロンプトはとりあえずそのままにしておいて、新しくコマンドプロンプトを立ち上げて、MongoShellを使ってみます。

以下のコマンドで起動しているMongoDBに接続することができます。

mongo

f:id:sugimomoto:20180104132745p:plain

データベースの作成

前回の記事でも書いたように、MongoDBはDocumentと呼ばれるRDBで言うところのレコード、CollectionとよばれるテーブルのようなDocumentを格納する箱と、それらを総括しているデータベースから成り立ちます。

まず、データベースを作らないと話が始まらないので、作ってみます。

f:id:sugimomoto:20180104132759p:plain

なお、初期状態ではTestというデータベースが自動的に作成され、そのデータベースに接続された状態でMongoShellが立ち上がりますが、今回はとりあえず一から作成します。

以下のようにコマンドを打つと、myNewDatabaseが作成されて、そのDBに接続された状態になります。

> use myNewDatabase
switched to db myNewDatabase

ちなみに、色々とコマンドを打ってみた感想なんですが、MongoDBのコマンドはすごく暗黙的に動作する感じがします。

上記コマンドもそうで、接続データベースの指定にもこのuse [DatabaseName]が利用できます。そして、そのデータベースが存在しなければ、自動的にデータベースを生成してくれます。

コマンドを打つときに間違えてしまうと、勝手にどんどん作成されちゃうかもしれないので注意しましょう。

ちなみにデータベースを削除する場合は対象のデータベースをuseした状態で、db.dropDatabase()です。

> db.dropDatabase()
{ "dropped" : "myNewDatabase", "ok" : 1 }

コレクションの作成・参照

続いてCollectionの作成を行います。

[db.createCollection("CollecctionName")]というコマンドで作成することができます。

> db.createCollection("CollecctionName")
{ "ok" : 1 }

作成したCollectionを削除する場合は[db.(CoolectionName).drop()] でいけます。

> db.CollecctionName.drop()
true

ドキュメントの作成・参照

それでは実際にドキュメントを作成していきたいと思います!

事前に作成したCollectionNameを添えて、[db.(CollectionName).insertOne()]のコマンドにBSONを渡して上げると、ドキュメントが生成できます。

> db.myCollection.insertOne({x: 1});
{
        "acknowledged" : true,
        "insertedId" : ObjectId("5a4c5ad1b3312b72b4d71991")
}

ここで注意したいのが、最初のuse Databaseのコマンドのように、ここでもdb.(存在しないCollection).insertOne()を実行すると、MongoDBはCollectionまで自動生成してくれます。

生成したドキュメントを見たい場合は[find()]コマンドを打ちます。

> db.myCollection.find()
{ "_id" : ObjectId("5a4c5ad1b3312b72b4d71991"), "x" : 1 }

ちなみに、_idフィールドは省略した状態でInsertを実行すると、自動的に生成されます。これがドキュメントにおける初期状態のキーとなります。

ちなみにMongoShellはJavascriptが使えるので例えば以下のようにforを書いたりすると、一気にドキュメントの作成が行えたりします。

for(var i = 0; i <= 100; i++){ db.myCollection.insertOne({x: i}) }

[db.state()]のコマンドで、中のコレクションやドキュメント件数がわかります。

> db.stats()
{
        "db" : "myNewDatabase",
        "collections" : 1,
        "views" : 0,
        "objects" : 102,
        "avgObjSize" : 33,
        "dataSize" : 3366,
        "storageSize" : 32768,
        "numExtents" : 0,
        "indexes" : 1,
        "indexSize" : 32768,
        "fsUsedSize" : 347442601984,
        "fsTotalSize" : 1008430542848,
        "ok" : 1
}

もうちょっと高度な検索

単純な検索は以下のように対象フィールドに一致する条件をそのままオブジェクトとして渡してあげる感じです。

> db.myCollection.find({x : 1})
{ "_id" : ObjectId("5a4c5ad1b3312b72b4d71991"), "x" : 1 }

これによって、xというフィールドが[1]のデータを抽出できます。

ちなみに、配列の検索が面白い。事前に以下のようなコレクションを作成して

> db.ArrayTest.insert({ name: "kazuya", loves: ['apple', 'orange']})
> db.ArrayTest.insert({ name: "hitomi", loves: ['banana', 'strawberry']})
> db.ArrayTest.insert({ name: "yuta", loves: ['apple', 'strawberry']})

findに配列の中身の一致条件として渡してあげると、検索結果が返ってくる。これは便利ー。

> db.ArrayTest.find({loves: 'apple'})
{ "_id" : ObjectId("5a4c7c92b3312b72b4d719f7"), "name" : "kazuya", "loves" : [ "apple", "orange" ] }
{ "_id" : ObjectId("5a4c7cd6b3312b72b4d719f9"), "name" : "yuta", "loves" : [ "apple", "strawberry" ] }

以下のようなオブジェクト構造の場合は[name.firstName]のような形でフィールドを指定することで、検索することが可能です。わかりやすいですねぇ。

> db.insetTest.insert({name : { firstName: "kazuya" , lastName : "sugimoto"}})
> db.insetTest.find({"name.firstName" : "kazuya"})
{ "_id" : ObjectId("5a4da862b3312b72b4d71a00"), "name" : { "firstName" : "kazuya", "lastName" : "sugimoto" } }

OR条件を使いたい場合はこんな感じ

> db.ArrayTest.find({ $or: [{loves : 'banana'},{loves : 'apple'}]})
{ "_id" : ObjectId("5a4c7c92b3312b72b4d719f7"), "name" : "kazuya", "loves" : [ "apple", "orange" ] }
{ "_id" : ObjectId("5a4c7ccbb3312b72b4d719f8"), "name" : "hitomi", "loves" : [ "banana", "strawberry" ] }
{ "_id" : ObjectId("5a4c7cd6b3312b72b4d719f9"), "name" : "yuta", "loves" : [ "apple", "strawberry" ] }

このあたりがやっぱり、ドキュメント指向のNoSQLらしい検索方法だなぁとほんと思います。

NotEqualも[$ne]を使ってこんな感じで書けます。

> db.ArrayTest.find({loves : {$ne : 'banana'}})
{ "_id" : ObjectId("5a4c7c92b3312b72b4d719f7"), "name" : "kazuya", "loves" : [ "apple", "orange" ] }
{ "_id" : ObjectId("5a4c7cd6b3312b72b4d719f9"), "name" : "yuta", "loves" : [ "apple", "strawberry" ] }

更新処理

更新処理はちょっと癖があります。わかりやすく[Update()]で行うんですが

> db.UpdateTest.insert({ name: "kazuya", age: 30})

上記ドキュメントのAgeを更新しようとして、以下のように書いてしまうと

> db.UpdateTest.update({ name: "kazuya"}, {age : 31})

なんと、Nameのフィールドが消えてしまいます。

> db.UpdateTest.find()
{ "_id" : ObjectId("5a4c8216b3312b72b4d719fa"), "age" : 31 }

フィールドを更新するというより、ドキュメントそのものを更新するという意味合いが強いためでしょうか。

なので、単一フィールドを更新したい場合は、以下のように[$set]修飾子を使う必要があります。

> db.UpdateTest.update({ name: "kazuya"}, {$set :{age : 31}})

ちなみにインクリメントも実行可能

> db.UpdateTest.update({ name: "kazuya"}, {$inc : {age : 1}})
{ "_id" : ObjectId("5a4c91efb3312b72b4d719fb"), "name" : "kazuya", "age" : 32 }

Upsertもサポートされていて、Updateの3個めの引数にtrueを足すことで、Upsertになる。これは便利ー。

まだ存在しないupdateの条件で、引数にtrueを加えると、初期状態のレコードが作成されます。

> db.UpdateTest.update({ name: "hitomi"}, {$inc : {age : 1}}, true)
> db.UpdateTest.find({name: "hitomi"})
{ "_id" : ObjectId("5a4c93b8efa7248cc7f9e97c"), "name" : "hitomi", "age" : 1 }

その後もう一度同じクエリを実行すると、ドキュメントは作成されず、インクリメントだけ行われる感じです。

> db.UpdateTest.update({ name: "hitomi"}, {$inc : {age : 1}}, true)
> db.UpdateTest.find({name: "hitomi"})
{ "_id" : ObjectId("5a4c93b8efa7248cc7f9e97c"), "name" : "hitomi", "age" : 1 }

あと注意したいのは、複数同時更新処理かなと思います。

例えば以下のようなデータで、Age30だけ、FlgをTrueにしたいとして

> db.BulkUpdateTest.insert({ name: "kazuya", age: 30,  flg: false})
> db.BulkUpdateTest.insert({ name: "hitomi",  age: 30,  flg: false})
> db.BulkUpdateTest.insert({ name: "yuta",  age: 29,  flg: false})

以下のようにUpdateをしてみると、期待としては、Age30の1,2のドキュメントがtrueになるようにイメージできるけど

> db.BulkUpdateTest.update({ age: 30}, {$set :  {flg: true}})

実は一個目しか更新されないらしい

> db.BulkUpdateTest.find()
{ "_id" : ObjectId("5a4c953eb3312b72b4d719fc"), "name" : "kazuya", "age" : 30, "flg" : true }
{ "_id" : ObjectId("5a4c9543b3312b72b4d719fd"), "name" : "hitomi", "age" : 30, "flg" : false }
{ "_id" : ObjectId("5a4c9549b3312b72b4d719fe"), "name" : "yuta", "age" : 29, "flg" : false }

検索結果全件を更新対象としたい場合は、updateの4つ目の引数にtureを付ける必要があるとのことです。

> db.BulkUpdateTest.update({ age: 30}, {$set :  {flg: true}},false,true)
> db.BulkUpdateTest.find()
{ "_id" : ObjectId("5a4c953eb3312b72b4d719fc"), "name" : "kazuya", "age" : 30, "flg" : true }
{ "_id" : ObjectId("5a4c9543b3312b72b4d719fd"), "name" : "hitomi", "age" : 30, "flg" : true }
{ "_id" : ObjectId("5a4c9549b3312b72b4d719fe"), "name" : "yuta", "age" : 29, "flg" : false }

削除

削除は[deleteMany({})]を使います。{}だけをパラメータに渡すと、対象コレクションの全ドキュメントを削除します。

> db.deleteTest.insert({name:"kazuya"})
> db.deleteTest.insert({name:"hitomi"})
> db.deleteTest.deleteMany({})
{ "acknowledged" : true, "deletedCount" : 2 }

findと同じように削除条件を指定すれば、そのドキュメントだけ削除することが可能です。

> db.deleteTest.deleteMany({name: "kazuya"})
{ "acknowledged" : true, "deletedCount" : 1 }

参考

The mongo Shell — MongoDB Manual 3.6

とりあえず読んでおいたほうがいいのは、MongoDBの薄い本。

http://www.cuspy.org/diary/2012-04-17/the-little-mongodb-book-ja.pdf


20180113修正

起動コマンドが間違ってた。[mongob]ではなく[mongod]でした。

mongod --dbpath c:\MongoDB\Test