【やってみよう】NoSQL「MongoDB」を10分で学べる記事
はじめに
今回の記事では、NoSQLのデータベースの代表格であるMongoDBの基礎知識を10分以内で理解することを目標に以下のことについて徹底解説する。
- MongoDBとは・NoSQLとは
- MongoDBの導入
- mongoシェルの基本的な操作
NoSQLとは
NoSQLとは簡単に言えば、データベースの1つである「リレーショナルデータベース」(RDB)とはまったく異なる方法でデータを処理・操作するデータベースを意味する。
RDBはデータを表形式で管理する。Excelのようなイメージで管理しているといいだろう。NoSQLはそのような形式でデータを管理しない。RDBにはMySQLなどの代表的なデータベースが存在するものの、それらRDB以外のデータベースを分類する言葉がNoSQLである。
さらに、NoSQLは「SQLを使わない(No)」という意味ではなく「SQLだけではない」を意味している点には十分に留意しておこう。
NoSQLの種類
NoSQLには主に以下のような種類がある。データの保存方法とアクセス方法によって区別される。
- キーバリュー型:Pythonの辞書型のように、キーと値がペアである特徴を有する基本的かつ単純なNoSQLの形式。
- カラム指向型:キーバリュー型の値の部分が1つ以上のカラム(列)になったもの。構造はRDBに近い
- ドキュメント指向型:キーに紐づくデータをドキュメント形式で格納できる。書き方の自由度が最も高く、複雑な要素を持つデータを格納できる。
- グラフ指向型:「ノード」と呼ばれるデータの実体と、「エッジ」と呼ばれるノードどうしの関係性を示す情報で構成されている。
MongoDBとは
MongoDBとは、NoSQLのデータベースの中では世界中で最も広く活用されている。NoSQLの種類の中ではドキュメント指向型に該当し、データを「ドキュメント」にJSONに類似した形式で格納して管理する。
アーキテクチャ
MongoDBのアーキテクチャは以下の通り。
- RDBのようにレコードをテーブルに格納するのではなく、「ドキュメント」と呼ばれる構造的データをJSONに類似している形式で表現し、そのドキュメントの場合を「コレクション」で管理する
- コレクションはスキーマレスなドキュメントで格納され、任意のフィールドを好きなときに追加できる
- ドキュメントには複雑な階層構造を持たせられる。それらの構造に含まれるフィールドを指定したクエリやインデックスの生成も簡単な指定で行える
- ソート・集計や値の検索が得意である。
MongoDBを使う理由
MongoDBは上述のアーキテクチャで構築されており、進化するデータスキーマに対応したスケーラブルなアプリケーションを開発するために、あらゆる種類の開発者の間で人気がある。
ドキュメント指向型のNoSQLデータベースとして、MongoDBは以下のようなことが得意だ。
- 構造化あるいは非構造化データを簡単に保存できる
- JSONに類似した形式を使ってドキュメントを保存する
- 大容量のデータを扱うことができ、大規模なデータロードに対応するために垂直方向にも水平方向にも拡張できる
MongoDBの特徴
- ドキュメント形式でデータを保存できる――MongoDBの公式サイトによると、ドキュメント形式はあらゆるプログラミング言語でデータを保存したり取得したりできるので迅速に開発を進められるそうだ。
- デプロイの選択肢が豊富――MongoDBはMongoDB Atlasを通じて主要なクラウド(AWSやGCPなど)で無料で活用できる。
- すぐに始められる――MongoDBは、一回インストールすればすぐにコードを書き始められる。
- 大容量のデータに対応できる
- エコシステムが成熟しており、情報量が豊富
- 公式ドキュメントやチュートリアルなどの開発に関するテクニックが豊富。初心者でも苦なく学びやすい。
MongoDBの問題点
ただし、MongoDBにも主に以下のような問題点がある。
- SQLが使えない。データベースの処理・操作はJavaScriptで行う。
- RDBのように高度な結合操作ができない。
- トランザクション処理が非常に煩雑。
複数のトランザクションを必要とするクエリが必要になる場合、MongoDBでの開発は適さない。MongoDBはどの開発にも通用する万能薬ではないので、システムに合わせて適材適所でRDBと使い分けなければならない。
MongoDBの導入コスト
- インストールが簡単
- 様々なOSやプログラミング言語に対応している
- JavaScriptに似たような形式で書けるので、フロントエンドでも親しみやすい
- レプリカやシャーディングの仕組みがRDBに比較して簡単
MongoDBの導入
MongoDBには、CommunityとEnterpriseの2つのプランが有る。前者はオープンソースとして提供されており、後半は追加の管理、認証や監視を得意とする。
インストール(Windows11の場合)
Windows11のローカル環境でMongoDBを動作するには、MongoDBシェル(mongosh
)が必要になる。mongosh
のインストール手順を参照して別途インストールする必要がある。
インストールの手順は以下の通り。
mongosh
)について
MongoDBシェル(mongosh
はMongoDBのデプロイに必要な機能を一式揃えたJavaScriptおよびNode.js v16.xの環境である。MongoDBシェルを使うと、MongoDBに対して直接クエリや操作のテストを行うことができる。ダウンロードはこちら。
MongoDBシェルをインストールしてシステム環境変数のPATH
に追加したら、MongoDBのデプロイを行うことができる。MongoDBシェルがサポートしているメソッドはこちらを参照。
MongoDBの基本用語
厳密には異なるものの、イメージとしては以下のような対比になる。
▼MongoDBとRDBの用語比較
MongoDB | RDB |
---|---|
データベース | データベース |
コレクション | テーブル |
ドキュメント | 行(レコード) |
フィールド | 列 |
MongoDBの基本操作
MongoDBシェルに接続する方法
mongosh
mongosh --host <host> --port <port> -u <user> -p <pwd>
mongosh "mongodb:#192.168.1.1:27017" # "mongodb:<ポート番号>"の形式で書く
mongo "mongodb+srv:#cluster-name.abcde.mongodb.net/<dbname>" --username <username> # MongoDB Atlasを使う場合
データベースを表示する
show dbs
db
データベースを切り替える
use <database_name>
コレクションの表示
show collections
JavaScriptファイルの実行
load("myScript.js")
MongoDBにおけるCRUD
原則、MongoDBはSQLを使わないのでJavaScriptでデータベースを操作する。
Create:データベースにデータを新規作成する
db.coll.insertOne({name: "Max"})
db.coll.insert([{name: "Max"}, {name:"Alex"}]) // データが整列される
db.coll.insert([{name: "Max"}, {name:"Alex"}], {ordered: false}) // {ordered: false}でデータの自動配列を無効にする
db.coll.insert({date: ISODate()})
db.coll.insert({name: "Max"}, {"writeConcern": {"w": "majority", "wtimeout": 5000}})
Read:データベースのデータを読み込む
db.coll.findOne() // 1つのドキュメントを出力する
db.coll.find() // returns a cursor - show 20 results - "it" to display more
db.coll.find().pretty()
db.coll.find({name: "Max", age: 32}) // この場合、nameとageのデータ両方を満たしているものを探す
db.coll.find({date: ISODate("2020-09-25T13:57:17.180Z")}) // 時刻指定。
db.coll.find({name: "Max", age: 32}).explain("executionStats") // or "queryPlanner" or "allPlansExecution"
db.coll.distinct("name")
// Count:要素の個数を取得
db.coll.count({age: 32}) // {age: 32}と書かれているすべてのドキュメントを出力
db.coll.estimatedDocumentCount() // この場合はすべてのドキュメントを出力する(countコマンドの上位互換)
db.coll.countDocuments({age: 32}) // コレクションあるいはビューのクエリ({age: 32}と一致する)と一致するドキュメントの数を出力する
// Comparison:要素を指定して特定する
db.coll.find({"year": {$gt: 1970}}) // 1970より大きい
db.coll.find({"year": {$gte: 1970}}) // 1970以上
db.coll.find({"year": {$lt: 1970}}) // 1970未満
db.coll.find({"year": {$lte: 1970}}) // 1970以下
db.coll.find({"year": {$ne: 1970}}) // 1970と等しくない
db.coll.find({"year": {$in: [1958, 1959]}}) // リスト内の要素が入っている
db.coll.find({"year": {$nin: [1958, 1959]}}) // リスト内の要素が入っていない
// Logical:論理式を使って条件を指定する。複数の条件を指定できる
db.coll.find({name:{$not: {$eq: "Max"}}})
db.coll.find({$or: [{"year" : 1958}, {"year" : 1959}]}) // 「または」を表現したいなら$or
db.coll.find({$nor: [{price: 1.99}, {sale: true}]}) // 複数の要素をすべて満たさないなら$nor
// こんな合わせ技もできてしまう
db.coll.find({
$and: [
{$or: [{qty: {$lt :10}}, {qty :{$gt: 50}}]},
{$or: [{sale: true}, {price: {$lt: 5 }}]}
]
})
// 要素を指定する
db.coll.find({name: {$exists: true}})
db.coll.find({"zipCode": {$type: 2 }})
db.coll.find({"zipCode": {$type: "string"}})
// Aggregation Pipeline
db.coll.aggregate([
{$match: {status: "A"}},
{$group: {_id: "$cust_id", total: {$sum: "$amount"}}},
{$sort: {total: -1}}
])
// テキスト検索
db.coll.find({$text: {$search: "cake"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}})
// 正規表現
db.coll.find({name: /^Max/}) # regex: starts by letter "M"
db.coll.find({name: /^Max$/i}) # regex case insensitive
// 配列
db.coll.find({tags: {$all: ["Realm", "Charts"]}})
db.coll.find({field: {$size: 2}}) # impossible to index - prefer storing the size of the array & update it
db.coll.find({results: {$elemMatch: {product: "xyz", score: {$gte: 8}}}})
// Projections
db.coll.find({"x": 1}, {"actors": 1}) # actors + _id
db.coll.find({"x": 1}, {"actors": 1, "_id": 0}) # actors
db.coll.find({"x": 1}, {"actors": 0, "summary": 0}) # all but "actors" and "summary"
// Sort(分類), skip(値を指定された数字分飛ばす), limit(出力されるデータの数)
db.coll.find({}).sort({"year": 1, "rating": -1}).skip(10).limit(3)
// Read Concern
db.coll.find().readConcern("majority")
Update:データベースのデータを更新する
db.coll.update({"_id": 1}, {"year": 2016}) // 注意! このように書くとドキュメント全てを上書きする。
db.coll.update({"_id": 1}, {$set: {"year": 2016, name: "Max"}}) // $setでデータを設定する
db.coll.update({"_id": 1}, {$unset: {"year": 1}})
db.coll.update({"_id": 1}, {$rename: {"year": "date"} }) // データの書き換え
db.coll.update({"_id": 1}, {$inc: {"year": 5}})
db.coll.update({"_id": 1}, {$mul: {price: NumberDecimal("1.25"), qty: 2}})
db.coll.update({"_id": 1}, {$min: {"imdb": 5}})
db.coll.update({"_id": 1}, {$max: {"imdb": 8}})
db.coll.update({"_id": 1}, {$currentDate: {"lastModified": true}})
db.coll.update({"_id": 1}, {$currentDate: {"lastModified": {$type: "timestamp"}}})
// Array(配列)
db.coll.update({"_id": 1}, {$push :{"array": 1}})
db.coll.update({"_id": 1}, {$pull :{"array": 1}})
db.coll.update({"_id": 1}, {$addToSet :{"array": 2}})
db.coll.update({"_id": 1}, {$pop: {"array": 1}}) // last element
db.coll.update({"_id": 1}, {$pop: {"array": -1}}) // first element
db.coll.update({"_id": 1}, {$pullAll: {"array" :[3, 4, 5]}})
db.coll.update({"_id": 1}, {$push: {scores: {$each: [90, 92, 85]}}})
db.coll.updateOne({"_id": 1, "grades": 80}, {$set: {"grades.$": 82}})
db.coll.updateMany({}, {$inc: {"grades.$[]": 10}})
db.coll.update({}, {$set: {"grades.$[element]": 100}}, {multi: true, arrayFilters: [{"element": {$gte: 100}}]})
// 複数のデータを更新する
db.coll.update({"year": 1999}, {$set: {"decade": "90's"}}, {"multi":true})
db.coll.updateMany({"year": 1999}, {$set: {"decade": "90's"}})
// 1つのデータを取り出して更新する
db.coll.findOneAndUpdate({"name": "Max"}, {$inc: {"points": 5}}, {returnNewDocument: true})
// データを挿入する
db.coll.update({"_id": 1}, {$set: {item: "apple"}, $setOnInsert: {defaultQty: 100}}, {upsert: true})
// データを置換する
db.coll.replaceOne({"name": "Max"}, {"firstname": "Maxime", "surname": "Beugnet"})
// データを上書き保存する
db.coll.save({"item": "book", "qty": 40})
// Write concern
db.coll.update({}, {$set: {"x": 1}}, {"writeConcern": {"w": "majority", "wtimeout": 5000}})
Delete:データベースのデータを削除する
db.coll.remove({name: "Max"})
db.coll.remove({name: "Max"}, {justOne: true})
db.coll.remove({}) // WARNING! Deletes all the docs but not the collection itself and its index definitions
db.coll.remove({name: "Max"}, {"writeConcern": {"w": "majority", "wtimeout": 5000}})
db.coll.findOneAndDelete({"name": "Max"})
MongoDBにおけるコレクション
Drop
db.coll.drop() // インデックスの定義とコレクションをすべて削除する
db.dropDataBase() // 現在使っているデータベースと、それに関連するデータファイルを削除する
コレクションの新規作成
// $jsonschemaでコレクションをつくる
db.createCollection("contacts", {
validator: {$jsonSchema: {
bsonType: "object",
required: ["phone"],
properties: {
phone: {
bsonType: "string",
description: "must be a string and is required"
},
email: {
bsonType: "string",
pattern: "@mongodb\.com$",
description: "must be a string and match the regular expression pattern"
},
status: {
enum: [ "Unknown", "Incomplete" ],
description: "can only be one of the enum values"
}
}
}}
})
MongoDBにおけるインデックス
インデックスの表示
db.coll.getIndexes() // インデックスを取得する
db.coll.getIndexKeys() // インデックスに対応する値を取得する
インデックスの新規作成
// Index Types
db.coll.createIndex({"name": 1}) // 1つのインデックス
db.coll.createIndex({"name": 1, "date": 1}) // 複数のインデックス
db.coll.createIndex({foo: "text", bar: "text"}) // 文字列のインデックス
db.coll.createIndex({"$**": "text"}) // ワイルドカードつきのインデックス
db.coll.createIndex({"userMetadata.$**": 1}) // ワイルドカードだけのインデックス
db.coll.createIndex({"loc": "2d"}) // 2d index
db.coll.createIndex({"loc": "2dsphere"}) // 2dsphere index
db.coll.createIndex({"_id": "hashed"}) // ハッシュ化されたインデックス。MongoDBにおける"_id"は自動で設定される
// インデックスを新規作成する
db.coll.createIndex({"lastModifiedDate": 1}, {expireAfterSeconds: 3600}) // TTL index
db.coll.createIndex({"name": 1}, {unique: true})
db.coll.createIndex({"name": 1}, {partialFilterExpression: {age: {$gt: 18}}}) // partial index
db.coll.createIndex({"name": 1}, {collation: {locale: 'en', strength: 1}}) // case insensitive index with strength = 1 or 2
db.coll.createIndex({"name": 1 }, {sparse: true})
インデックスのドロップ
db.coll.dropIndex("name_1") // "name_1"というインデックスを削除する
インデックスを表示・非表示に設定する
db.coll.hideIndex("name_1") //インデックスの非表示
db.coll.unhideIndex("name_1") //インデックスの表示
ストリームの変更
ストリームを変更することで、リアルタイムのデータ変更に簡単にアクセスできる。MongoDBで開発されたアプリケーションは、変更されたストリームを活用して、単一のコレクション、データベース、または配置全体のすべてのデータ変更を実施し、それらに即座に対応できるようになる。
// db.coll.watch:引数にデータベースのインデックスを指定する
watchCursor = db.coll.watch( [ { $match : {"operationType" : "insert" } } ] )
// cursor.isExhausted():カーソルが閉じられ、バッチ内に残りのオブジェクトがない場合はtrueを返す(要は、カーソル内にドキュメントが残っていない場合)
while (!watchCursor.isExhausted()){
// カーソル内にドキュメントがある場合、JSONを出力する
if (watchCursor.hasNext()){
print(tojson(watchCursor.next()));
}
}
MongoDBの学習・開発で役立つサイト
awesome-mongodb
- MongoDBの学習・開発に役立つツール、WEB記事などをまとめたGitHubリポジトリ。MongoDBを学習したいなら必見である。
Mongo Playground
- ブラウザ上でMongoDBの挙動を確認できる。
- 会員登録なしで簡単に使える。
- MongoDBのデータベースのテストを簡単にしたいなら必見
Mongoku
- MongoDBのデータベースの現在の状況をリアルタイムで確認できるデスクトップツール
MongoDB Crash Cource 2022
- MongoDBの基礎知識、インストールなどを徹底解説されている
- MongoDBをざっくり学びたいなら必見
MongoDB Crash Course
- MongoDBの基礎知識だけではなく、MongoDBとPythonを連携して簡単なアプリケーションを開発するチュートリアル付き。
- リアルタイムでプログラムやデータベースの挙動を確認できる。
Tutorial: Build a RESTful API With CRUD Functionality Using Node.js, Express.js & MongoDB - Medium
- MongoDBとNode.jsで簡単にREST APIを開発するチュートリアル。
- 難易度はそこまで高くない。(ただしExpress.jsの前提知識がいることには注意)
Let’s build a ToDo application using FastAPI and MongoDB - Medium
- MongoDBとFastAPIを連携して簡単なTodoアプリを開発できるチュートリアル。
- MongoDBでの開発フローを体験したいなら必見である。
MongoDB vs. MySQL: which is better? - Medium
- MongoDB(NoSQL)とMySQL(RDB)の違いを簡潔にまとめられている。
- データベースの操作にSQLを使うメリット・デメリットを理解したいなら一読すべき。
- 結局のところ、MongoDBとMySQLはトレードオフの関係にある。
やってみようNoSQL MongoDBを最速で理解する
- MongoDBに関する記事を体系的にまとめられている優良記事。
- 日本語でMongoDBを学びたいならこの記事を絶対に確認しておこう。
余談
MongoDBの公式ドキュメントが非常に丁寧なので、初心者あるいはNoSQLに興味がある人は絶対に学ぶことをオススメする。
MongoDBは学べば学ぶほど奥が深いデータベースである。
参考サイト
Discussion