エラーメッセージ
クエリを実行した際、データが返ってくる代わりに、コンソールに次のようなストレスの溜まるエラーが表示されることがあります。
MongoServerError: Projection cannot have a mix of inclusion and exclusion
このエラーは、MongoDBがフィールドのフィルタリングにおいて明確な戦略を必要とするために発生します。データベースに対して「何が必要か」または「何が不要か」のどちらかを正確に伝える必要があります。両方を同時に行うことは許可されていません。
エラーが発生する理由
プロジェクション(Projection)は、結果の形式を整えるための手法です。find()クエリでは、第2引数がプロジェクションオブジェクトになります。MongoDBは厳格なロジックを適用しており、「ホワイトリスト」か「ブラックリスト」のどちらかを選択しなければなりません。
- 包含(ホワイトリスト): 必要なフィールドを
1で指定します。MongoDBはそれ以外のすべてを無視します。例:{ username: 1, email: 1 } - 除外(ブラックリスト): 非表示にしたいフィールドを
0で指定します。MongoDBはそれ以外のすべてを返します。例:{ password: 0, ssn: 0 }
これらを { username: 1, password: 0 } のように混在させようとすると、競合が発生します。MongoDBは、リストにないフィールドを「包含」として扱うべきか「除外」として扱うべきか判断できないため、クエリを停止させます。
唯一の例外: _id フィールド
_id フィールドは例外的な存在です。MongoDBはデフォルトで _id を含めるため、他のフィールドをホワイトリスト形式で指定しながら、_id だけを明示的に除外することが許可されています。{ email: 1, _id: 0 } のようなプロジェクションは完全に有効であり、API開発では非常によく使われます。
クエリの修正方法
ステップ1: 競合箇所を特定する
クエリのコードを確認してください。一般的な Node.js や Mongoose の環境では、エラーは通常以下のような行に潜んでいます。
// ❌ 1と0を混在させているためエラーになります
const user = await db.collection('users').find({}, {
projection: { name: 1, hashed_password: 0 }
});
ステップ2: 1つの戦略を選択する
特定のユースケースにどちらのアプローチが適しているかを決定します。通常、セキュリティの観点からはホワイトリスト形式(包含)が推奨されます。
オプションA: 包含(Inclusion)に統一する
50個のフィールドがあるドキュメントから3つのフィールドだけが必要な場合は、その3つだけをリストアップします。この方がコードがすっきりし、パフォーマンスも向上します。
// ✅ 正解: name と email のみを返します
db.collection('users').find({}, {
projection: { name: 1, email: 1, _id: 0 }
});
オプションB: 除外(Exclusion)に統一する
データの大部分が必要だが、トークンやソルト値のような機密性の高い内部フィールドを隠したい場合に使用します。
// ✅ 正解: 機密フィールド以外のすべてを返します
db.collection('users').find({}, {
projection: { password: 0, internal_notes: 0, __v: 0 }
});
集計(Aggregation)と $project ステージ
同じロジックが集計(aggregation)パイプラインにも適用されます。$project ステージで 1 と 0 を混在させると、パイプライン全体が失敗します。以下は、壊れた集計を修正する方法です。
// ❌ 壊れた集計
{ $project: { total_price: 1, 'customer.address': 0 } }
// ✅ 修正済み (包含のみ)
{ $project: { total_price: 1, order_date: 1, items: 1 } }
本番環境で役立つヒント
- セキュリティ優先: 常に包含(ホワイトリスト)を優先してください。後でスキーマに新しい機密フィールドを追加した場合、除外ベースのクエリでは、手動でブラックリストを更新するまで、その新しいフィールドが誤って漏洩してしまう可能性があります。
- パフォーマンスの向上: プロジェクションは単にデータを整理するだけではありません。プロジェクションにインデックスの一部であるフィールドのみが含まれている場合、MongoDBはディスクからドキュメント全体を読み取ることなく結果を返すことができます。これは「カバードクエリ(Covered Query)」と呼ばれます。
- Mongoose のショートカット: Mongoose を使用している場合は、
.select('name email -_id')のような文字列構文を使用できます。Mongoose は背後で 1 と 0 の処理を賢く処理してくれます。
クイックサマリー
エラーを解消するには、プロジェクションオブジェクトを確認してください。同じブロック内に 1 と 0 が混在している場合は、どちらか一方を削除します。「何が必要か」を定義するために 1 を残すか、「何を隠すか」を定義するために 0 を残します。ただし、_id: 0 だけは例外として併用できることを覚えておいてください。

