TL;DR: クイック修正
このエラーは、$size の制約によって発生します。この演算子は配列を必要としますが、代わりに null や欠損フィールドが見つかったためです。即座に修正するには、フィールドを $ifNull で囲み、フォールバックとして空の配列 [] を指定します。
// エラーが発生する書き方:
{ $project: { count: { $size: "$tags" } } }
// 安全な書き方:
{
$project: {
count: {
$size: { $ifNull: [ "$tags", [] ] }
}
}
}
パイプラインがクラッシュする理由
MongoDBの柔軟性は諸刃の剣です。厳格なスキーマがないため、あるドキュメントには tags 配列があっても、次のドキュメントでは完全に省略されていることがあります。$size がその欠損フィールドに遭遇すると、単にゼロを返すのではなく、集計全体を停止させてしまいます。
たとえ1,000,000個の完璧なドキュメントがあったとしても、フィールドが欠落したレコードが1つあるだけでクエリは失敗します。これは通常、以下の3つの理由で発生します:
- フィールドの欠損: キーがドキュメントに含まれていない。
- Null値: フィールドは存在するが、アプリケーションやインポートスクリプトによって明示的に
nullが設定されている。 - データ型の不一致: 配列を期待している箇所に、文字列(
"none"など)やオブジェクトが含まれている。
実践的な解決策
方法 1: $ifNull を使用する(標準的な修正)
主にフィールドの欠損やnull値を扱う場合は、$ifNull が最適です。これはセーフティネットとして機能します。フィールドが欠損している場合、$size に空の配列を渡すため、正しく 0 というカウントが返されます。
db.posts.aggregate([
{
$project: {
title: 1,
comment_count: {
$size: { $ifNull: [ "$comments", [] ] }
}
}
}
])
方法 2: $isArray を使用する(より堅牢なアプローチ)
本番環境のデータはしばしば整理されていません。comments がブール値や文字列になっているドキュメントが見つかることもあります。$ifNull ではこれらの型をキャッチできません。完全に安全を期すには、計測前に $isArray を使用してデータを確認します。
db.posts.aggregate([
{
$addFields: {
comment_count: {
$cond: {
if: { $isArray: "$comments" },
then: { $size: "$comments" },
else: 0
}
}
}
}
])
この条件分岐ロジックにより、コレクション内にどのようなデータ型が潜んでいても、パイプラインが停止することはありません。
方法 3: $match で事前にフィルタリングする(パフォーマンス重視)
「不完全な」ドキュメントを一切処理したくない場合もあります。パイプラインの最初で配列を持つドキュメントのみをフィルタリングすることで、後続のステージの負荷を軽減できます。これはインデックスを活用できるため、多くの場合で高速です。
db.posts.aggregate([
{
$match: {
comments: { $exists: true, $type: "array" }
}
},
{
$project: {
comment_count: { $size: "$comments" }
}
}
])
修正の検証
推測ではなく、テストしましょう。「汚れた」データを含む小さなコレクションを作成し、集計があらゆるエッジケースを処理できるか確認します。
db.test_collection.insertMany([
{ _id: 1, items: ["apple", "banana"] }, // 有効な配列
{ _id: 2 }, // フィールドの欠損
{ _id: 3, items: null }, // Nullフィールド
{ _id: 4, items: "配列ではない文字列" } // 文字列(これには方法2を使用してください!)
]);
// 検証を実行
db.test_collection.aggregate([
{
$project: {
count: {
$cond: [
{ $isArray: "$items" },
{ $size: "$items" },
0
]
}
}
}
]);
修正が成功すれば、エラーメッセージを表示することなく、2、0、0、0 というカウントが返されます。

