MongoDB Academy · レッスン

要素演算子と型チェック

$exists と $type を使ってフィールドの存在や型を検索し、任意フィールドや型が混在するデータを扱います。

レッスン 3/413 ステップ

「要素演算子と型チェック」はCoddyKit上の無料MongoDB Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはMongoDB Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 MongoDB Academyコースには全4レッスンが含まれています。

柔軟なスキーマでの任意フィールド

MongoDBの柔軟なスキーマでは、コレクション内の一部のドキュメントに、他のドキュメントにはないフィールドが存在する場合があります。たとえば、ユーザードキュメントに任意のphoneNumberフィールドがあるとします。入力したユーザーもいれば、入力していないユーザーもいます。SQLでは、すべての行でNULL値を使ってこの状態を扱います。MongoDBでは、一部のドキュメントにそのフィールドが存在しないだけです。

この違いはクエリに重要な影響を与えます。MongoDBの要素演算子(主に$existsと$type)を使うと、フィールドの値ではなく、フィールドの存在や型に基づいてクエリできます。

$exists:フィールドの存在を確認する

$exists: trueは、指定されたフィールドを持つドキュメントに一致します(値に関係なく、値がnullの場合も含みます)。$exists: falseは、そのフィールドが完全に存在しないドキュメントに一致します。

この違いは微妙ですが重要です。{ phone: null }を持つドキュメントは、フィールドが存在していて値がnullであるため、{ phone: { $exists: true } }に一致します。{ phone: { $exists: false } }に一致するのは、phoneフィールド自体が存在しないドキュメントだけです。

// Find users who have provided a phone number (field exists)
db.users.find({ phone: { $exists: true } });

// Find users who never provided a phone number (field absent)
db.users.find({ phone: { $exists: false } });

// Key distinction:
// { phone: null }    => $exists: true (field exists, value is null)
// { name: 'Alice' }  => $exists: false (no phone field at all)

// Combined: field exists AND is not null
db.users.find({ phone: { $exists: true, $ne: null } });

nullと存在しないフィールドのクエリ

MongoDBでnullを使った等価フィルターには、初心者が驚きやすい2つの動作があります。{ phone: null }は、phoneが明示的にnullであるドキュメントと、phoneがまったく存在しないドキュメントの両方に一致します。MongoDBでは、等価比較において存在しないフィールドを暗黙的にnullとみなすためです。

「フィールドがnull」と「フィールドが存在しない」を区別したい場合は、$existsと$eq: nullを組み合わせます。実際の多くの用途では、どちらも「電話番号が入力されていない」ことを意味するため、同じように扱えます。

// Matches BOTH null and missing field:
db.users.find({ phone: null });
// Returns: { name: 'Alice', phone: null }
//      AND: { name: 'Bob' }  (no phone field)

// Only where field is explicitly null:
db.users.find({ phone: { $eq: null, $exists: true } });
// Returns: { name: 'Alice', phone: null }
// NOT: { name: 'Bob' } (missing field)

// Only where field is missing:
db.users.find({ phone: { $exists: false } });

$type:BSON型でクエリする

MongoDBはスキーマが柔軟であるため、priceのようなフィールドが、ほとんどのドキュメントではNumberである一方、一部の古いレコードでは誤ってStringとして保存されていることがあります。$type演算子を使うと、フィールドの値のBSON型に基づいてドキュメントをフィルタリングできます。

型は名前(例:'string'、'int'、'date')またはBSON型番号(例:Stringを表す2、Int32を表す16)で指定できます。型名のほうが読みやすく、推奨されます。

// Find documents where price is a string (data quality issue)
db.products.find({ price: { $type: 'string' } });
// These need to be fixed - price should be a number

// Find documents where price is a number (any numeric type)
db.products.find({ price: { $type: ['double', 'int', 'long', 'decimal'] } });
// $type accepts an array - matches any of the listed types

// Check for boolean
db.settings.find({ enabled: { $type: 'bool' } });

一般的なBSON型名

$typeクエリで最もよく使われるBSON型名は次のとおりです。

  • 'double' — 64ビット浮動小数点数(JSのデフォルトの数値型)
  • 'string' — UTF-8文字列
  • 'object' — 埋め込みドキュメント(サブオブジェクト)
  • 'array' — 配列
  • 'binData' — バイナリデータ
  • 'objectId' — ObjectId
  • 'bool' — 真偽値
  • 'date' — Date
  • 'null' — null値
  • 'int' — 32ビット整数
  • 'long' — 64ビット整数
  • 'decimal' — Decimal128
// Real-world use: find documents where tags is an array
db.products.find({ tags: { $type: 'array' } });

// Find docs where createdAt is a date (not a string)
db.events.find({ createdAt: { $type: 'date' } });

// Audit: find any document where _id is not an ObjectId
db.users.find({ _id: { $not: { $type: 'objectId' } } });
// These might be docs with custom string _ids

「number」エイリアス

便利なショートカットとして、'number'型エイリアスがあります。これは、double、int、long、decimalというすべての数値BSON型に一致します。4つすべての数値型名を配列に記述するのと同じ意味ですが、より簡潔です。

具体的な精度に関係なく、フィールドが何らかの数値であるすべてのドキュメントを検索したい場合は、'number'を使用してください。数値であるべきフィールドが誤って文字列として保存されていないかを特定するデータ品質チェックに、特に便利です。

// 'number' alias matches double, int, long, decimal
db.products.find({ price: { $type: 'number' } });
// Same as: { price: { $type: ['double', 'int', 'long', 'decimal'] } }

// Data quality check: find price fields that are strings
// These should be numbers:
db.products.find({ price: { $type: 'string' } });

// Fix those docs:
db.products.find({ price: { $type: 'string' } }).forEach(doc => {
  db.products.updateOne(
    { _id: doc._id },
    { $set: { price: parseFloat(doc.price) } }
  );
});

スキーママイグレーションでの$existsの使用

MongoDBのスキーママイグレーションでは、既存のドキュメントに新しい必須フィールドを追加することがよくあります。$existsを使うと、まだ更新が必要なドキュメントや、すでにマイグレーション済みのドキュメントを見つけられます。

一般的なマイグレーションの流れは、まず{ newField: { $exists: false } }を使って古い形式のドキュメントをすべて見つけ、次にデフォルト値を設定した新しいフィールドを追加するというものです。$exists: falseの状態で残っているドキュメント数を数えることで進捗を確認できます。この段階的なマイグレーションは、MongoDBの柔軟なスキーマによって一時的に古い形式と新しい形式のドキュメントを共存させられるため、ダウンタイムなしで実行できます。

// Migration: add 'timezone' field with default value to legacy users
async function migrateTimezone() {
  const batchSize = 1000;
  let processed = 0;

  while (true) {
    const result = await db.collection('users').updateMany(
      { timezone: { $exists: false } },  // Legacy docs without field
      { $set: { timezone: 'UTC' } },     // Add default
      { limit: batchSize }               // Process in batches
    );
    processed += result.modifiedCount;
    if (result.modifiedCount === 0) break;
  }
  console.log('Migrated:', processed);
}

$existsと$typeの組み合わせ

$existsと$typeを1つのクエリで組み合わせると、フィールドが存在し、かつ特定の型であるドキュメントを見つけられます。これは、フィールドが誤った型に設定されているドキュメントと、単にフィールドが存在しないドキュメントを区別したいデータ品質監査に便利です。

たとえば、ageが数値であるべきコレクションでは、次のドキュメントを個別に数えたい場合があります。(1) ageフィールド自体がないドキュメント、(2) ageが文字列であるドキュメント(入力ミス)、(3) ageが有効な数値であるドキュメント。

// Audit 'age' field data quality
const total = await db.collection('users').countDocuments({});

const missingAge = await db.collection('users')
  .countDocuments({ age: { $exists: false } });

const ageIsString = await db.collection('users')
  .countDocuments({ age: { $type: 'string' } });

const ageIsNumber = await db.collection('users')
  .countDocuments({ age: { $type: 'number' } });

console.log(`Total: ${total}, Missing: ${missingAge}, String: ${ageIsString}, Number: ${ageIsNumber}`);

$expr:フィールド間の比較

$expr演算子を使うと、通常のfind()フィルター内で集計式を使用できます。これにより、標準のクエリ演算子では実現できない、フィールド間の比較が可能になります。

たとえば、「割引額が元の価格の50%を超える注文を検索する」には、同じドキュメント内にある2つのフィールド、discountとoriginalPriceを使って、割引額と計算した割合を比較する必要があります。$exprを使うと、$gt、$multiply、$divideなどの式演算子によって、このような関係を表現できます。

// Find products where salePrice < originalPrice * 0.7 (>30% off)
db.products.find({
  $expr: {
    $lt: [
      '$salePrice',
      { $multiply: ['$originalPrice', 0.7] }
    ]
  }
});

// Find orders where quantity ordered > quantity in stock
db.orders.find({
  $expr: { $gt: ['$quantityOrdered', '$quantityInStock'] }
});

$existsクエリのインデックス

$existsを使用するクエリでは、インデックスのサポート状況が異なります。フィールドに対するインデックスは、{ field: { $exists: false } }には役立ちません。フィールドが存在しない場合、そのフィールドはインデックスに含まれないため、MongoDBはすべてのドキュメントをスキャンする必要があります。一方、{ field: { $exists: true } }ではスパースインデックスを使用できます。スパースインデックスは、インデックス対象のフィールドを持つドキュメントのエントリだけを保存するため、フィールドが存在するドキュメントを高速に検索できます。

オプションのフィールドを持つドキュメントを頻繁に検索する場合は、db.collection.createIndex({ field: 1 }, { sparse: true })を使ってスパースインデックスを作成してください。

// Sparse index: only indexes docs where 'proSubscriptionId' exists
db.users.createIndex(
  { proSubscriptionId: 1 },
  { sparse: true }
);

// This query now uses the sparse index efficiently:
db.users.find({ proSubscriptionId: { $exists: true } });
// Only pro subscribers are in the index

// Regular (non-sparse) index would include null entries for all users
// Sparse index is smaller and faster for optional-field queries

データ品質監査のワークフロー

とを使った実践的なデータ品質監査のワークフローでは、まずコレクション内のすべての異なるフィールドパターンを特定して不整合を見つけ、次にフィールドの型ごとにドキュメントを分類し、最後に対象を絞ったupdateMany呼び出しを記述してデータを正規化します。

MongoDBの集計パイプラインを使うと、監査を簡単に行えます。フィールドをでし、それぞれの型を持つドキュメント数を数えます。これにより、修正を始める前にデータ品質の問題を明確に把握できます。

// Audit: how many docs have price as each type?
db.products.aggregate([
  {
    : {
      _id: { : '' },  // Group by BSON type of price
      count: { : 1 }
    }
  }
]);
// Results like:
// [{ _id: 'double', count: 4820 },
//  { _id: 'string', count: 3 },
//  { _id: 'missing', count: 12 }]
// Now you know exactly what to fix!

簡単な確認

このレッスンで学んだMongoDBとNoSQLデータベースの概念について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、次のことを学びました。$existsクエリではフィールドの存在または不在を確認できます。フィールドが存在しない場合と値がnullの場合は異なり、$exists: trueと$ne: nullを組み合わせて区別できます。$typeではBSONの型名(string、number、date、arrayなど)によってフィルタリングでき、スキーマが柔軟なコレクションでのデータ品質監査に不可欠です。また、スパースインデックスは、フィールドを持つドキュメントだけをインデックス化することで、オプションのフィールドに対する$exists: trueクエリを効率的にサポートします。次は、柔軟なテキスト検索のための正規表現クエリとパターンマッチングについて学びます。

無料で開始

AI チューターと学ぶ JavaScript — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
30
レッスン
120

よくある質問

「要素演算子と型チェック」レッスンは無料ですか?

はい。「要素演算子と型チェック」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、MongoDB Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 MongoDB Academyコースには全4レッスンが含まれています。

「要素演算子と型チェック」で何を学びますか?

$exists と $type を使ってフィールドの存在や型を検索し、任意フィールドや型が混在するデータを扱います。 ブラウザで直接実行するハンズオンコードでMongoDB Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

MongoDB Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのMongoDB Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「要素演算子と型チェック」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このMongoDB Academyレッスンでコードを書いて実行できますか?

はい。すべてのMongoDB Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 比較演算子:$eq、$gt、$lt、$in
  2. 論理演算子:$and、$or、$nor、$not
  3. 要素演算子と型チェック
  4. 正規表現クエリとパターンマッチング
← MongoDB Academyに戻る