MongoDB: 配列とネストドキュメントのクエリ
最終更新:2026-08-26
ネストドキュメントと配列はMongoDBの核心機能です。ドキュメントモデルを最大限に活用するには、これらをマスターする必要があります。
本コースでは、配列クエリ、ネストドキュメントのドット記法、$elemMatchの完全一致、配列更新操作について詳しく解説します。
1. 学習内容
- ドット記法を使用したネストドキュメントのクエリ
- 配列フィールドのクエリ(単一要素、複数要素)
- $elemMatch:同一配列内の要素の完全一致
$位置プレースホルダーによる更新- 配列インデックス(マルチキーインデックス)
- 配列と参照の設計上のトレードオフ
2. eコマースデータエンジニアの実話
(1) 課題:ネストドキュメントのクエリで誤った結果が返ってくる
Aliceは商品レビューシステムを管理しており、クエリのバグに遭遇しました:
// ❌ 反例:「評価>= 4かつ'good'キーワードを含む」コメントを検索
db.products.find({
"reviews.rating": { $gte: 4 },
"reviews.content": /good/i
});
// ⚠️ 問題:異なるコメントがマッチする可能性(あるコメントが高評価、別のコメントがキーワードを含む)
// 期待:単一コメントが両方の条件を同時に満たす
(2) $elemMatchによる完全一致の解決策
// ✅ 正例:$elemMatchを使用
db.products.find({
reviews: {
$elemMatch: {
rating: { $gte: 4 },
content: /good/i
}
}
});
// ✅ 正しい:以下の全条件を満たす単一コメント
graph TB
subgraph "埋め込みドキュメント方式"
A1[productsコレクション] --> A2[ドキュメント1<br/>address: {<br/> city: 東京<br/> country: 日本<br/>}]
end
subgraph "参照方式ドキュメント"
B1[productsコレクション] --> B2[ドキュメント1<br/>addressId: ObjectId]
B3[addressesコレクション] --> B4[ドキュメント1<br/>city: 東京]
B2 -.->|検索| B3
end
style A2 fill:#d4edda
3. ネストドキュメントのドット記法
概念説明明: 埋め込みドキュメントとは、フィールド値として別のドキュメントを含むドキュメントを指します。MongoDBは最大100レベルまでのネストをサポートしますが、本番環境では3レベルを超えないことを推奨します。ドット記法は"field.subfield.subsubfield"構文を使用してネストフィールドにアクセスする方法で、MongoDBでのネストデータのクエリと更新の核心メカニズムです。
仕組み: MongoDBはドット記法を解析する際、外側から内側へとレイヤーごとにフィールドパスを特定します。クエリエンジンは"specs.screen.size"をdoc.specs.screen.sizeに分解し、BSONドキュメントツリーをレベルごとに走査します。インデックスもドット記法フィールドをサポートします。例:{ "specs.screen.size": 1 }で効率的なインデックスを作成可能です。
graph TD
A[BSONドキュメント] --> B[specs: Object]
B --> C[screen: Object]
C --> D["size: '6.5'"]
C --> E["type: 'OLED'"]
B --> F[battery: Object]
F --> G["capacity: '4500mAh'"]
H["specs.screen.size"] -->|ドット記法の解析| D
I["specs.battery.capacity"] -->|ドット記法の解析| G
style D fill:#d4edda
style G fill:#d4edda
| 観点 | 説明 |
|---|---|
| 適用シーン | 階層が固定で、まとめて読み書きされるデータ(住所、仕様など) |
| 非適用シーン | 階層が不確定、または頻繁に独立して更新されるデータ(参照を使用) |
| パフォーマンスへの影響 | 単一レベル≈通常フィールド、複数レベルは走査が必要。推奨≤3レベル |
| インデックス対応 | 完全対応。{ "a.b.c": 1 }は通常インデックスと同等 |
(1) ネストドキュメントのクエリと更新
// === ネストドキュメント構造 ===
db.products.insertOne({
sku: 'PHONE-001',
title: 'Smartphone X',
specs: {
screen: { size: '6.5', type: 'OLED' },
battery: { capacity: '4500mAh', type: 'Li-Po' }
}
});
// === ドット記法でのクエリ ===
db.products.find({ "specs.screen.size": '6.5' });
// === 複数レベルのネスト ===
db.products.find({ "specs.battery.capacity": '4500mAh' });
// === ネストフィールドの更新 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "specs.battery.capacity": "5000mAh" } }
);
4. 配列フィールドのクエリ
概念説明明: 配列はMongoDBドキュメントモデルで最も柔軟なデータ構造の一つです。単一フィールドで複数の値を格納でき、配列フィールドに対するMongoDBのクエリは「任意マッチ」の原則に従います。配列内のいずれか1つの要素が条件を満たせば、そのドキュメントが選択されます。これにより配列クエリは強力である反面、予期しない結果を生む可能性があります。
仕組み: MongoDBのクエリエンジンは配列フィールドに対して「暗黙の展開マッチング」戦略を使用します。{ tags: '5g' }をクエリすると、エンジンは配列要素を1つずつチェックし、いずれかの要素が'5g'に等しければマッチと見なします。複数条件クエリ(例:{"reviews.rating": {$gte:4}, "reviews.content": /good/})では、各条件が独立して配列要素にマッチするため、異なる要素にマッチする可能性があります。これが$elemMatchが存在する理由です。
graph LR
A[クエリ条件] --> B{配列マッチング戦略}
B -->|単一条件| C[任意の要素がマッチ<br/>tags: '5g']
B -->|複数条件ドット記法| D[各条件が独立してマッチ<br/>異なる要素にマッチする可能性]
B -->|複数条件$elemMatch| E[同一要素が<br/>全条件を同時に満たす必要]
C --> F[✅ シンプルで効率的]
D --> G[⚠️ 精度が低い可能性]
E --> H[✅ 完全一致]
style F fill:#d4edda
style G fill:#fff3cd
style H fill:#d4edda
| 検索方法 | マッチングルール | 適用シーン |
|---|---|---|
{ tags: '5g' } |
任意の要素が'5g'に等しい | 単一値マッチ |
{ tags: { $all: ['5g','amoled'] } } |
全ての値を含む必要がある | 複数値 |
{ tags: { $in: ['5g','fast'] } } |
いずれかの値を含む | 複数値またはマッチ |
{ "tags.0": '5g' } |
インデックス位置を指定 | 位置マッチ |
{ tags: { $size: 3 } } |
配列長の完全一致 | 長さフィルタリング |
(1) 単一要素マッチ
// === 配列が指定要素を含む ===
db.products.find({ tags: '5g' });
// tags配列が'5g'を含む全商品
// === 配列が指定要素を全て含む($all)===
db.products.find({ tags: { $all: ['5g', 'amoled'] } });
// '5g'と'amoled'の両方を含む必要がある
// === 配列がいずれかの要素を含む($in)===
db.products.find({ tags: { $in: ['5g', 'fast-charging'] } });
// '5g'または'fast-charging'を含む
(2) インデックスによる配列要素のクエリ
// === 配列の最初の要素を取得 ===
db.products.find({ "tags.0": '5g' });
// tags[0] = '5g'
// === 配列の2番目の要素を取得 ===
db.products.find({ "tags.1": 'amoled' });
// tags[1] = 'amoled'
// === 配列要素の範囲クエリ ===
db.products.find({ "tags.0": { $in: ['new', 'sale'] } });
(3) 配列長のチェック
// === 配列長の完全一致 ===
db.products.find({ tags: { $size: 3 } });
// タグ数 = 3
// === 配列長の範囲クエリ ===
db.products.find({
$expr: { $gt: [{ $size: '$tags' }, 3] }
});
// タグ数 > 3
5. $elemMatch 完全一致
概念説明明: $elemMatchはMongoDBの配列フィールド専用に設計された完全一致演算子です。異なる要素がそれぞれ一部の条件を満たすのではなく、同一配列要素が全てのクエリ条件を同時に満たすことを保証します。配列要素がオブジェクト(コメントや成績など)で複数フィールドの結合クエリが必要な場合、$elemMatchが唯一の正しい選択です。
仕組み: $elemMatchは配列内の各要素に対して全条件を順次適用します。ある要素が全ての条件チェックを同時に通過した場合のみ、マッチと見なされます。ドット記法との主な違い:ドット記法では複数条件がそれぞれ配列をスキャンし、異なる要素にマッチする可能性があります。$elemMatchは単一要素が全テストを通過する必要があります。
sequenceDiagram
participant Query as クエリエンジン
participant Doc as ドキュメント<br/>reviews: [{rating:5,content:"bad"},<br/>{rating:3,content:"good"}]
Note over Query,Doc: ドット記法クエリ: reviews.rating>=4 AND reviews.content=/good/
Query->>Doc: 条件1: rating>=4 → 要素0にマッチ
Query->>Doc: 条件2: /good/ → 要素1にマッチ
Doc-->>Query: ⚠️ 異なる要素がそれぞれ一部の条件を満たす → このドキュメントを返却
Note over Query,Doc: $elemMatch検索
Query->>Doc: 要素0: rating>=4 ✅, /good/ ❌ → マッチしない
Query->>Doc: 要素1: rating>=4 ❌ → マッチしない
Doc-->>Query: ✅ いずれの要素も全条件を満たさない → 返却しない
| 比較観点 | ドット記法 | $elemMatch |
|---|---|---|
| マッチ粒度 | 各条件が独立してマッチ | 同一要素が全条件を満たす必要 |
| 構文 | { "a.b": x, "a.c": y } |
{ a: { $elemMatch: { b: x, c: y } } } |
| 精度 | ⚠️ 異なる要素にマッチする可能性 | ✅ 単一要素まで正確 |
| パフォーマンス | わずかに高速(インデックスを個別に使用可能) | わずかに低速(各要素をチェック必要) |
| インデックス対応 | 複数条件を個別にインデックスで処理可能 | 複合インデックスが必要 |
(1) 核心となる問題
// === 反例:異なる配列要素がマッチ ===
db.products.find({
"reviews.rating": { $gte: 4 },
"reviews.content": /good/i
});
// マッチする可能性:review1.rating=5, review2.content="good"
// つまり、異なるコメントがそれぞれ条件を満たす
// ✅ 正例:$elemMatchで同一要素をマッチ
db.products.find({
reviews: {
$elemMatch: {
rating: { $gte: 4 },
content: /good/i
}
}
});
// 単一コメントが以下の全条件を同時に満たす必要がある
(2) 複雑な条件
// === 複数条件elemMatch ===
db.products.find({
reviews: {
$elemMatch: {
rating: { $gte: 4 },
helpful: { $gt: 10 },
createdAt: { $gte: new Date('2026-01-01') }
}
}
});
// === ネストelemMatch ===
db.students.find({
courses: {
$elemMatch: {
name: 'Math',
score: { $gte: 90 },
assignments: {
$elemMatch: {
submitted: true,
grade: { $gte: 80 }
}
}
}
}
});
6. 配列更新修飾子
概念説明明: MongoDBは配列フィールドの操作専用に一連の更新修飾子を提供します:$push(追加)、$pull(削除)、$addToSet(重複排除して追加)、$pop(先頭・末尾要素を削除)。$位置プレースホルダーとarrayFiltersを組み合わせることで、配列全体を置き換えることなく、特定の要素を正確に更新できます。
仕組み: 配列更新修飾子はMongoDBサーバー上で原子的に実行されます。$pushは要素を配列の末尾に追加します。$addToSetは追加前に要素が既に存在するかをチェックします。$pullは条件に基づいて要素を削除します。$プレースホルダーはクエリ条件に最初にマッチした配列要素の位置を指します。$[]は全要素を指します。$[filter]はarrayFiltersと組み合わせて条件付き一括更新を行います。
graph TB
A[配列更新修飾子] --> B[$push<br/>要素追加]
A --> C[$pull<br/>条件削除]
A --> D[$addToSet<br/>重複排除して追加]
A --> E[$pop<br/>先頭・末尾削除]
F[位置演算子] --> G[$<br/>最初のマッチを更新]
F --> H[$[]<br/>全要素を更新]
F --> I[$[filter]<br/>条件付き一括更新<br/>arrayFiltersと併用]
style B fill:#d4edda
style C fill:#f8d7da
style D fill:#cce5ff
style I fill:#fff3cd
| 修飾子 | 機能 | 例 |
|---|---|---|
$push |
要素追加(重複可能) | { $push: { tags: 'new' } } |
$each |
$pushで複数追加 | { $push: { tags: { $each: ['a','b'] } } } |
$addToSet |
重複排除して追加 | { $addToSet: { tags: 'new' } } |
$pull |
条件で削除 | { $pull: { tags: 'old' } } |
$pop |
先頭/末尾を削除 | { $pop: { tags: 1 } }(末尾を削除) |
$ |
最初にマッチした要素を更新 | { $set: { "reviews.$.flagged": true } } |
$[] |
全要素を更新 | { $set: { "reviews.$[].status": "ok" } } |
$[f] |
条件付き一括更新 | { arrayFilters: [{ "f.rating": {$lt:2} }] } |
(1) $push:要素追加
// === 単一追加 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $push: { tags: 'bestseller' } }
);
// === 複数追加($each)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $push: { tags: { $each: ['5g', 'amoled', 'fast-charging'] } } }
);
(2) $pull:要素削除
// === 指定値を削除 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { tags: 'old-tag' } }
);
// === 複数削除 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { tags: { $in: ['outdated1', 'outdated2'] } } }
);
(3) $addToSet:重複排除して追加
// === 既に存在する場合は追加しない ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $addToSet: { tags: '5g' } }
);
// tagsに'5g'が既に存在する場合は変更なし
// === 複数追加($each)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $addToSet: { tags: { $each: ['5g', 'amoled'] } } }
);
(4) $pop:先頭/末尾削除
// === 末尾を削除(1)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pop: { tags: 1 } }
);
// === 先頭を削除(-1)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pop: { tags: -1 } }
);
(5) 位置演算子:$ / $[] / $[filter]
// === $演算子:最初にマッチした要素を更新 ===
db.products.updateOne(
{ sku: 'PHONE-001', "reviews.rating": { $lt: 2 } },
{ $set: { "reviews.$.flagged": true } }
);
// === $[]演算子:全要素を更新 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[].status": "approved" } }
);
// === $[filter]演算子:条件付き一括更新 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[r].flagged": true } },
{
arrayFilters: [{ "r.rating": { $lt: 2 } }]
}
);
// rating < 2のコメントのみを対象
▶ サンプル 1:レビューシステムの実践
// === シーン:商品レビューシステム ===
// 1. レビューを追加
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$push: {
reviews: {
userId: 'user_001',
rating: 5,
content: 'Excellent phone!',
helpful: 0,
createdAt: new Date()
}
}
}
);
// 2. 参考になった投票をインクリメント
db.products.updateOne(
{ sku: 'PHONE-001', "reviews.userId": 'user_001' },
{ $inc: { "reviews.$.helpful": 1 } }
);
// 3. 低評価のレビューにフラグを立てる
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[r].flagged": true } },
{ arrayFilters: [{ "r.rating": { $lt: 2 } }] }
);
// 4. 特定ユーザーのレビューを削除
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { reviews: { userId: 'user_001' } } }
);
// 5. レビュー数を最大100件に制限
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$push: {
reviews: {
$each: [newReview],
$slice: -100
}
}
}
);
7. 配列と参照の設計トレードオフ
概念説明明: MongoDBでは一対多リレーションをモデリングする際、埋め込み(配列)と参照の2つのアプローチがあります。埋め込みは「親と子が常に一緒に読み書きされる」シーンに適し(例:商品レビュー)、参照は「子が頻繁に独立して更新される」シーンに適します(例:注文アイテム)。適切な選択がパフォーマンスとデータ整合性の鍵となります。
比較分析:
| 観点 | 埋め込み(配列) | 参照 |
|---|---|---|
| 読み取りパフォーマンス | ✅ 1回のクエリで完了 | ⚠️ 複数クエリまたは$lookupが必要 |
| 書き込みパフォーマンス | ⚠️ 親ドキュメント全体を更新 | ✅ 子ドキュメントのみ更新 |
| 配列サイズ上限 | 16MB(ドキュメント全体) | 制限なし |
| インデックス | マルチキーインデックス | 通常インデックス |
| アトミック性 | ✅ 単一ドキュメント | ❌ トランザクションが必要 |
| 複雑さ | 低い | 中程度 |
graph TB
A[一対多リレーション] --> B{データ特徴}
B -->|小規模・頻繁に一緒に読み取り| C[埋め込み<br/>配列]
B -->|大規模・独立して更新| D[参照<br/>別コレクション]
C --> C1[例:商品レビュー<br/>ユーザータグ<br/>最大100件]
D --> D1[例:注文アイテム<br/>ユーザーログ<br/>無制限]
style C1 fill:#d4edda
style D1 fill:#cce5ff
(1) 埋め込みが適するケース
// ✅ 適する:商品レビュー(小規模、親と一緒に表示)
const productWithReviews = {
sku: 'PHONE-001',
title: 'Smartphone X',
reviews: [
{ userId: 'user_001', rating: 5, content: 'Great!' },
{ userId: 'user_002', rating: 4, content: 'Good' }
// 通常 < 100件
]
};
// 検索:1回で完了
db.products.findOne({ sku: 'PHONE-001' });
(2) 参照が適するケース
// ✅ 適する:注文アイテム(大規模、独立して更新)
const order = {
_id: 'order_001',
userId: 'user_001',
status: 'pending'
};
const orderItems = [
{ orderId: 'order_001', sku: 'PHONE-001', qty: 2, price: 599.99 },
{ orderId: 'order_001', sku: 'CASE-001', qty: 1, price: 29.99 }
];
// 検索:$lookupで結合
db.orders.aggregate([
{ $match: { _id: 'order_001' } },
{
$lookup: {
from: 'orderItems',
localField: '_id',
foreignField: 'orderId',
as: 'items'
}
}
]);
(3) ハイブリッドパターン
// === サブセットパターン ===
const product = {
sku: 'PHONE-001',
title: 'Smartphone X',
// 最新の10件のみ埋め込み(表示用)
recentReviews: [
{ userId: 'user_001', rating: 5, content: 'Great!' }
// 最新10件
],
totalReviews: 1525,
averageRating: 4.5
};
// 全レビューは別コレクションで参照
8. マルチキーインデックス
概念説明明: マルチキーインデックスは配列フィールドに作成される特殊なインデックスです。配列内の各要素に対してインデックスエントリが作成されるため、配列フィールドのクエリパフォーマンスが大幅に向上します。1つのドキュメントに最大1つのマルチキーインデックスしか作成できません(複合インデックスの場合)。
仕組み: マルチキーインデックスを作成すると、MongoDBは配列の各要素を個別のインデックスエントリとしてインデックスします。{ tags: 1 }を作成すると、tags: ['5g', 'amoled']のドキュメントに対して5gとamoledそれぞれのインデックスエントリが作成されます。検索時、MongoDBは条件にマッチするインデックスエントリを見つけ、対応するドキュメントを返します。
graph TB
A[配列フィールドにインデックス作成] --> B[マルチキーインデックス]
B --> C["tags: ['5g', 'amoled']"]
C --> D["インデックスエントリ:<br/>'5g' → doc_id<br/>'amoled' → doc_id"]
D --> E[検索: { tags: '5g' }]
E --> F[インデックスから直接マッチ]
style B fill:#cce5ff
style F fill:#d4edda
(1) マルチキーインデックスの作成
// === 配列フィールドにインデックス作成 ===
db.products.createIndex({ tags: 1 });
// マルチキーインデックス(自動)
// === ネスト配列フィールドにインデックス作成 ===
db.products.createIndex({ "reviews.rating": 1 });
// reviews配列内のratingフィールドにインデックス
// === 複合マルチキーインデックス ===
db.products.createIndex({ "reviews.rating": 1, "reviews.createdAt": -1 });
(2) マルチキーインデックスの制約
// ⚠️ 制約:複合インデックスに複数のマルチキーフィールドは不可
db.products.createIndex({ tags: 1, categories: 1 });
// エラー:両方が配列の場合
// ✅ 解決策:一方のみ配列の場合は作成可能
db.products.createIndex({ tags: 1, category: 1 });
// tagsが配列、categoryが単一値 → OK
▶ サンプル 2:マルチキーインデックスの実践
// === シーン:レビューシステム ===
// 1. レビューのratingフィールドにインデックス作成
db.products.createIndex({ "reviews.rating": 1 });
// 2. 高評価レビューを高速検索
db.products.find({
reviews: {
$elemMatch: { rating: { $gte: 4 } }
}
});
// インデックスを使用して高速検索
// 3. 複合インデックスでさらに最適化
db.products.createIndex({
"reviews.rating": 1,
"reviews.createdAt": -1
});
// 4. 最新の高評価レビューを検索
db.products.find({
reviews: {
$elemMatch: {
rating: { $gte: 4 },
createdAt: { $gte: new Date('2026-01-01') }
}
}
});
9. 総合実習
概念説明明: 実践的な配列とネストドキュメントの使用は、eコマースレビューシステム、学生履修管理、タグ管理など幅広いシーンに適用できます。これらのシーンでは、$elemMatchで正確なクエリ、$push/$pull/$addToSetで配列更新、マルチキーインデックスでパフォーマンス最適化を行います。
▶ サンプル 3:学生履修管理システム
// === 学生ドキュメント構造 ===
db.students.insertOne({
studentId: 'STU-001',
name: 'Alice',
courses: [
{
courseId: 'CS101',
name: 'Introduction to Computer Science',
instructor: 'Dr. Smith',
score: 92,
semester: 'Fall 2025',
assignments: [
{ id: 'HW1', grade: 95, submitted: true },
{ id: 'HW2', grade: 88, submitted: true }
]
},
{
courseId: 'MATH201',
name: 'Linear Algebra',
instructor: 'Prof. Johnson',
score: 85,
semester: 'Fall 2025',
assignments: [
{ id: 'HW1', grade: 90, submitted: true },
{ id: 'HW2', grade: 80, submitted: true }
]
}
],
updatedAt: new Date()
});
// === 検索:CS101を履修し、かつ成績>= 90の学生 ===
db.students.find({
courses: {
$elemMatch: {
courseId: 'CS101',
score: { $gte: 90 }
}
}
});
// === 検索:いずれかの科目で未提出の課題がある学生 ===
db.students.find({
"courses.assignments": {
$elemMatch: { submitted: false }
}
});
// === 更新:CS101の成績を95に更新 ===
db.students.updateOne(
{ studentId: 'STU-001', "courses.courseId": 'CS101' },
{ $set: { "courses.$.score": 95 } }
);
// === 更新:MATH201に新しい課題を追加 ===
db.students.updateOne(
{ studentId: 'STU-001', "courses.courseId": 'MATH201' },
{
$push: {
"courses.$.assignments": {
id: 'HW3',
grade: null,
submitted: false
}
}
}
);
// === 更新:未提出の課題を全てフラグ付け ===
db.students.updateOne(
{ studentId: 'STU-001' },
{ $set: { "courses.$[].assignments.$[a].late": true } },
{
arrayFilters: [
{ "a.submitted": false }
]
}
);
❓ よくある質問
$pushは無条件に追加(重複可能)。$addToSetは既に存在する場合は追加しない(重複排除)。重複を避けたい場合は$addToSetを使用。$とarrayFiltersを組み合わせます。例:"courses.$.assignments.$[a].grade": 95とarrayFilters: [{ "a.id": "HW1" }]。{ tags: 1 }で作成すると、{ tags: '5g' }クエリが高速化されます。複合インデックスでは1つの配列フィールドのみ使用可能。📖 まとめ
- ドット記法:
"field.subfield"でネストフィールドにアクセス - 配列クエリ:デフォルトで「任意マッチ」、
$allで全要素マッチ、$sizeで長さマッチ $elemMatch:同一配列要素が全条件を同時に満たす必要がある- 配列更新:
$push(追加)、$pull(削除)、$addToSet(重複排除)、$pop(先頭/末尾削除) - 位置演算子:
$(最初のマッチ)、$[](全要素)、$[filter](条件付き) - 設計トレードオフ:小規模・頻繁に読み取り→埋め込み、大規模・独立更新→参照
- マルチキーインデックス:配列フィールドの検索を高速化
📝 練習問題
- 基礎問題(⭐):商品ドキュメントにネストされた
specsフィールドを作成し、ドット記法でspecs.screen.sizeを検索してください。 - 基礎問題(⭐):
$pushで商品に3つのタグを追加し、$addToSetで重複排除をテストしてください。 - 応用問題(⭐⭐):
$elemMatchを使用して「評価>= 4かつ参考になった投票> 10」のレビューを検索してください。 - 応用問題(⭐⭐):
arrayFiltersを使用して低評価(rating < 2)のレビューにフラグを立ててください。 - チャレンジ(⭐⭐⭐):学生履修管理システムを設計し、ネスト配列クエリ、位置更新、マルチキーインデックスを含めてください。