この記事の要点
• insertOne/find/updateOne/deleteOneがCRUDの基本4操作
• aggregateパイプラインで$match→$group→$sortの集計が強力
• インデックス設計とexplain()による実行計画確認が性能の鍵
概要
MongoDB は BSON 形式でドキュメントを格納する代表的なドキュメント指向データベースです。本チートシートでは mongosh (MongoDB Shell) から実行するコマンドを中心に、実務で頻出する CRUD・クエリ・集計・インデックス操作を整理します。コマンドは MongoDB 7.x / 8.x の公式マニュアルに基づいています。
接続と基本
| コマンド | 説明 |
|---|
mongosh | ローカルの MongoDB に接続 |
mongosh "mongodb://host:27017" | 任意ホストに接続 |
mongosh "mongodb+srv://user:pass@cluster/db" | Atlas SRV 接続 |
show dbs | データベース一覧 |
use mydb | データベースを選択(存在しなければ作成予約) |
db | 現在のデータベース名 |
show collections | コレクション一覧 |
db.stats() | DB 統計 |
db.version() | バージョン確認 |
データベース・コレクション管理
| コマンド | 説明 |
|---|
db.createCollection("users") | コレクション作成 |
db.users.drop() | コレクション削除 |
db.dropDatabase() | 現在の DB を削除 |
db.users.renameCollection("members") | リネーム |
db.users.countDocuments({}) | 件数(正確) |
db.users.estimatedDocumentCount() | メタ情報からの概算件数 |
CRUD 操作
挿入
| コマンド | 説明 |
|---|
db.users.insertOne({name: "Ada"}) | 1 件挿入 |
db.users.insertMany([{...}, {...}]) | 複数挿入 |
db.users.insertMany(docs, {ordered: false}) | 失敗時も継続 |
ポイント: insertManyで{ordered: false}を指定すると、途中でエラーが発生しても残りのドキュメントの挿入を継続します。大量データ投入時に便利です。
検索
| コマンド | 説明 |
|---|
db.users.find() | 全件取得 |
db.users.find({age: {$gte: 18}}) | 条件付き取得 |
db.users.findOne({_id: id}) | 1 件取得 |
db.users.find({}, {name: 1, _id: 0}) | 射影(projection) |
db.users.find().sort({age: -1}) | 降順ソート |
db.users.find().limit(10).skip(20) | ページング |
db.users.distinct("country") | 値の一覧 |
更新
| コマンド | 説明 |
|---|
db.users.updateOne({_id}, {$set: {age: 30}}) | 1 件更新 |
db.users.updateMany({active: true}, {$inc: {points: 1}}) | 複数更新 |
db.users.replaceOne({_id}, newDoc) | 置換 |
db.users.updateOne(filter, update, {upsert: true}) | 無ければ挿入 |
db.users.findOneAndUpdate(filter, update, {returnDocument: "after"}) | 更新後ドキュメントを返却 |
削除
| コマンド | 説明 |
|---|
db.users.deleteOne({_id}) | 1 件削除 |
db.users.deleteMany({active: false}) | 複数削除 |
db.users.findOneAndDelete(filter) | 削除したドキュメントを返却 |
実践メモ: findOneAndUpdateに{returnDocument: "after"}を付けると、更新後のドキュメントが返されます。アトミックな更新+取得に最適です。
クエリ演算子
比較
| 演算子 | 説明 |
|---|
$eq | 等しい |
$ne | 等しくない |
$gt / $gte | より大きい / 以上 |
$lt / $lte | より小さい / 以下 |
$in | いずれかに一致 |
$nin | いずれにも一致しない |
論理
| 演算子 | 説明 |
|---|
$and | すべて満たす |
$or | いずれかを満たす |
$nor | どれも満たさない |
$not | 否定 |
要素
| 演算子 | 説明 |
|---|
$exists | フィールドの有無 |
$type | BSON 型で絞り込み |
配列
| 演算子 | 説明 |
|---|
$all | 指定値をすべて含む |
$elemMatch | 要素ごとの複合条件 |
$size | 配列長で絞り込み |
注意: $setとreplaceOneは異なります。$setは指定フィールドのみ更新、replaceOneはドキュメント全体を置換します。意図しないフィールド消失に注意しましょう。
更新演算子
| 演算子 | 説明 |
|---|
$set | フィールドを設定 |
$unset | フィールドを削除 |
$inc | 数値を加算 |
$mul | 数値を乗算 |
$min / $max | 最小/最大で更新 |
$rename | フィールドをリネーム |
$push | 配列に追加 |
$addToSet | 重複しないよう追加 |
$pull | 条件に合う要素を削除 |
$pop | 末尾/先頭を削除 |
$currentDate | 現在日時を設定 |
実用スニペット集
1. 部分一致(正規表現)
db.users.find({ name: /^Ada/i });
2. 複合条件
db.orders.find({
status: "paid",
total: { $gte: 1000 },
createdAt: { $gte: new Date("2026-01-01") }
});
3. OR 条件
db.users.find({
$or: [{ role: "admin" }, { plan: "premium" }]
});
4. 配列要素の複合条件
db.products.find({
tags: { $elemMatch: { $in: ["sale", "new"] } }
});
5. Upsert
db.counters.updateOne(
{ _id: "visits" },
{ $inc: { value: 1 } },
{ upsert: true }
);
6. 集計:グループ集計
db.orders.aggregate([
{ $match: { status: "paid" } },
{ $group: { _id: "$userId", total: { $sum: "$amount" }, count: { $sum: 1 } } },
{ $sort: { total: -1 } },
{ $limit: 10 }
]);
7. 集計:JOIN($lookup)
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{ $unwind: "$user" }
]);
8. 集計:フィールド追加
db.users.aggregate([
{
$addFields: {
fullName: { $concat: ["$firstName", " ", "$lastName"] }
}
}
]);
9. 集計:日別集計
db.events.aggregate([
{
$group: {
_id: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
]);
10. インデックス作成
db.users.createIndex({ email: 1 }, { unique: true });
db.orders.createIndex({ userId: 1, createdAt: -1 });
db.posts.createIndex({ title: "text", body: "text" });
11. テキスト検索
db.posts.find({ $text: { $search: "mongodb index" } });
12. インデックス一覧・削除
db.users.getIndexes();
db.users.dropIndex("email_1");
13. 実行計画の確認
db.users.find({ email: "a@b.co" }).explain("executionStats");
14. Bulk Write
db.users.bulkWrite([
{ insertOne: { document: { name: "A" } } },
{ updateOne: { filter: { name: "A" }, update: { $set: { active: true } } } },
{ deleteOne: { filter: { name: "old" } } }
]);
15. トランザクション(レプリカセット必須)
const session = db.getMongo().startSession();
session.startTransaction();
try {
session.getDatabase("shop").orders.insertOne({...}, { session });
session.getDatabase("shop").stock.updateOne({...}, { $inc: { qty: -1 } }, { session });
session.commitTransaction();
} catch (e) {
session.abortTransaction();
throw e;
} finally {
session.endSession();
}
16. エクスポート / インポート(シェル外)
| コマンド | 説明 |
|---|
mongoexport --uri="mongodb://localhost/shop" --collection=users --out=users.json | JSON エクスポート |
mongoimport --uri="mongodb://localhost/shop" --collection=users --file=users.json | JSON インポート |
mongodump --uri="mongodb://localhost" --out=backup/ | バイナリダンプ |
mongorestore --uri="mongodb://localhost" backup/ | バイナリリストア |
ポイント: 集計パイプラインでは$matchをパイプラインの先頭に配置し、インデックスを効かせることが性能最適化の基本です。
集計ステージ早見表
| ステージ | 説明 |
|---|
$match | 条件で絞り込み |
$project | フィールド整形 |
$group | グループ化・集計 |
$sort | ソート |
$limit / $skip | 件数制御 |
$lookup | 結合 |
$unwind | 配列を展開 |
$addFields | フィールド追加 |
$set | $addFields のエイリアス |
$unset | フィールド削除 |
$count | 件数を返す |
$facet | 複数パイプラインを並列実行 |
$bucket | バケット集計 |
$merge | 結果を別コレクションに書き込み |
$out | 結果を完全置換で書き込み |
集計演算子(式)早見表
| 演算子 | 説明 |
|---|
$sum / $avg / $min / $max | 合計/平均/最小/最大 |
$first / $last | 先頭/末尾 |
$push / $addToSet | 配列化 |
$concat | 文字列連結 |
$toUpper / $toLower | 大文字/小文字 |
$dateToString | 日付を文字列化 |
$cond | if 式 |
$ifNull | null 時のデフォルト |
注意: COLLSCAN(コレクションスキャン)がexplain()で表示されたら、インデックスが効いていない証拠。適切なインデックスを追加しましょう。
実践メモ: TTLインデックスを使えば、セッションやログなどの一時データを自動削除できます。expireAfterSecondsで有効期限を指定します。
トラブルシューティング
| 状況 | 原因 / 対処 |
|---|
E11000 duplicate key | ユニークインデックス違反。既存値を確認 |
| クエリが遅い | explain("executionStats") で COLLSCAN を確認し、インデックス追加 |
$lookup が遅い | localField / foreignField にインデックスを張る |
| トランザクション不可 | スタンドアロンではなくレプリカセット/シャードが必要 |
_id を変更したい | 不可。新ドキュメント挿入 + 旧削除で対応 |
| 日付比較が効かない | 文字列ではなく ISODate / new Date() を使用 |
Tips
- 本番ではユニーク制約や必須フィールドに必ずインデックスを張る。
find() は Cursor を返す。大量データは for (const doc of cursor) で反復。
$match はパイプラインの先頭に置き、インデックスを効かせる。
ObjectId は生成時刻を含むので、_id で時系列ソートが概ね可能。
- スキーマ検証には
$jsonSchema バリデータを使う。
- Atlas を使う場合はネットワーク IP 許可リストの設定を忘れない。
代表的なドライバ(Node.js 例)
import { MongoClient, ObjectId } from "mongodb";
const client = new MongoClient(process.env.MONGO_URL);
await client.connect();
const db = client.db("shop");
const users = db.collection("users");
const r = await users.insertOne({ name: "Ada", createdAt: new Date() });
const u = await users.findOne({ _id: new ObjectId(id) });
const list = await users
.find({ active: true })
.project({ name: 1 })
.sort({ createdAt: -1 })
.limit(20)
.toArray();
await users.updateOne({ _id: r.insertedId }, { $set: { active: true } });
await users.deleteOne({ _id: r.insertedId });
await client.close();
代表的なドライバ(Python 例 / PyMongo)
from pymongo import MongoClient, ASCENDING
from bson import ObjectId
client = MongoClient("mongodb://localhost:27017")
db = client["shop"]
db.users.insert_one({"name": "Ada"})
doc = db.users.find_one({"_id": ObjectId(id_str)})
for u in db.users.find({"active": True}).sort("createdAt", -1).limit(10):
print(u)
db.users.create_index([("email", ASCENDING)], unique=True)
ユーザ・ロール管理
| コマンド | 説明 |
|---|
db.createUser({user, pwd, roles}) | ユーザ作成 |
db.updateUser(user, {roles}) | ロール更新 |
db.dropUser(user) | ユーザ削除 |
db.grantRolesToUser(user, roles) | ロール付与 |
db.revokeRolesFromUser(user, roles) | ロール取消 |
db.getUsers() | ユーザ一覧 |
db.auth(user, pwd) | 認証 |
代表的な組込ロール: read, readWrite, dbAdmin, userAdmin, clusterAdmin, root。
バックアップとリストア
mongodump --uri="mongodb://localhost" --out=backup/$(date +%F)
mongodump --uri="mongodb://localhost" --db=shop --out=backup/
mongorestore --uri="mongodb://localhost" --nsFrom='shop.*' --nsTo='shop_restored.*' backup/
よくある設計パターン
| パターン | 説明 |
|---|
| 埋め込み(Embedding) | 親ドキュメントに子を配列で持つ。1:1 や少数の 1:N に向く |
| 参照(Referencing) | 子ドキュメントに親の _id を持つ。多対多や大きな子に向く |
| Bucket | 時系列を時間単位でまとめて 1 ドキュメントに |
| Computed | 集計結果をあらかじめフィールドに保持 |
| Schema Versioning | schemaVersion で段階的に移行 |
インデックスの種類
| 種類 | 説明 |
|---|
| 単一フィールド | {field: 1} の基本形 |
| 複合 | {a: 1, b: -1} 前方プレフィクスが効く |
| マルチキー | 配列フィールドに自動で作成される |
| テキスト | {field: "text"}、$text 検索で利用 |
| 2dsphere | GeoJSON 地理空間検索 |
| ハッシュ | シャーディング用 |
| ワイルドカード | {"$**": 1} 未知のキーに対応 |
| TTL | {createdAt: 1} + expireAfterSeconds で自動削除 |
| 部分 | partialFilterExpression で対象を絞る |
| 一意 | {unique: true} で重複禁止 |
スキーマ検証($jsonSchema)
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["email", "createdAt"],
properties: {
email: { bsonType: "string", pattern: "^.+@.+$" },
age: { bsonType: "int", minimum: 0, maximum: 150 },
createdAt: { bsonType: "date" }
}
}
},
validationLevel: "strict",
validationAction: "error"
});
Change Streams(変更通知)
const cs = db.orders.watch([
{ $match: { operationType: { $in: ["insert", "update"] } } }
]);
while (!cs.isClosed()) {
if (cs.hasNext()) printjson(cs.next());
}
参考リソース
この技術を体系的に学びたいですか?
未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。
メールで無料相談する
← 一覧に戻る