問題の概要MongoDBの集計(aggregation)パイプラインを構築している際、すべて順調に見えていたのに突然クラッシュすることがあります。配列を個別のドキュメントに展開するはずの $unwind ステージで問題が発生したのです。期待していたクリーンなデータの代わりに、次のような苛立たしいエラーが表示されます。
PlanExecutor error during aggregation :: caused by :: $unwind: value at end of field path 'yourFieldName' was not an array
これが起こる理由は、$unwind の制約によるものです。このステージは厳密に配列を分解するために設計されています。パイプラインが処理するドキュメントの中に、対象のフィールドが文字列、整数、または真偽値であるものが1つでも含まれていると、操作全体が停止してしまいます。
根本原因原因のほとんどは、データの不整合です。MongoDBのスキーマレスな性質は柔軟性において優れていますが、アプリケーションのロジックが厳密でない場合、「汚れた」データが混入する可能性があります。例えば、以下のようなケースでこの問題が発生します。
- レガシースクリプトが、単一のタグを配列(
["marketing"])ではなく文字列("marketing")として保存していた。- フロントエンドのバグにより、ユーザーがIDのリストではなく単一の数値IDを送信できてしまった。- CSVや外部APIからデータをインポートした際、単一の項目に対して配列型が厳密に強制されていなかった。## エラーの修正方法### 方法1:クイックフィルター有効な配列を含むドキュメントのみを処理すればよい場合は、$unwindの直前に$matchステージを追加するのが最も簡単な解決策です。これがセーフティゲートとして機能し、互換性のあるデータのみが後続の処理に渡されるようになります。
db.collection.aggregate([
{
$match: {
"yourFieldName": { $type: "array" }
}
},
{
$unwind: "$yourFieldName"
}
])
$type: "array" を使用することで、フィールドが文字列、数値、または null であるドキュメントを実質的に無視できます。これにより、予期しない型でクラッシュすることなく、パイプラインをスムーズに実行し続けることができます。
方法2:実行時に変換するデータの形式が正しくないからといって、無視できない場合もあります。フィールドが単一の値か配列かに関わらず、すべてのドキュメントを処理する必要がある場合は、$unwind ステージの前に $addFields を使用して、単一の値を配列でラップします。
db.collection.aggregate([
{
$addFields: {
"yourFieldName": {
$cond: {
if: { $isArray: "$yourFieldName" },
then: "$yourFieldName",
else: {
$ifNull: [ ["$yourFieldName"], [] ]
}
}
}
}
},
{
$unwind: "$yourFieldName"
}
])
このロジックはフィールドの型をチェックします。すでに配列であればそのまま維持し、単一の値(例えば文字列の "urgent")であれば、ブラケットで囲んで ["urgent"] に変換します。これにより、問題なく展開(unwind)できるようになります。
方法3:恒久的な修正(データのクリーンアップ)クエリでのその場しのぎの対応も役立ちますが、データベースをクリーンアップすることが最も信頼できる長期的な解決策です。1回限りのマイグレーションスクリプトを実行して、コレクション内の配列でない値をすべて見つけて変換します。
// フィールドが存在するが配列ではないすべてのドキュメントを更新する
db.collection.find({
"yourFieldName": { $exists: true, $not: { $type: "array" } }
}).forEach(function(doc) {
db.collection.updateOne(
{ _id: doc._id },
{ $set: { "yourFieldName": [doc.yourFieldName] } }
);
});
検証手順修正がうまくいったと思い込まないようにしましょう。変更を適用した後、簡単な診断チェックを行ってデータがクリーンであることを確認します。countDocuments を使用して、対象外のデータを探します。
db.collection.countDocuments({
"yourFieldName": { $exists: true, $not: { $type: "array" } }
})
結果が 0 であれば、$unwind ステージは安全です。もし数百や数千といった数値が表示される場合は、まだ対処が必要な不整合なデータが残っています。

