요소 연산자와 유형 검사
$exists와 $type을 사용하여 필드의 존재 여부나 유형을 조회하고 선택적 데이터 또는 여러 유형이 섞인 데이터를 처리합니다.
요소 연산자와 유형 검사은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Optional Fields in Flexible Schemas
MongoDB's flexible schema means some documents in a collection may have fields that others lack. A user document might have an optional phoneNumber field—some users provided it, others did not. In SQL, you would handle this with a NULL value in every row. In MongoDB, the field simply does not exist in some documents.
This difference has important implications for querying. MongoDB's element operators—primarily $exists and $type—let you query based on the presence or type of a field, rather than its value.
$exists: Checking Field Presence
$exists: true matches documents that have the specified field (regardless of its value, even if the value is null). $exists: false matches documents where the field is completely absent.
This distinction is subtle but important: a document with { phone: null } does match { phone: { $exists: true } } because the field exists—it is just null. Only a document with no phone field at all matches { phone: { $exists: false } }.
// 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 vs Missing Field Queries
MongoDB's equality filter with null has a dual behavior that often surprises beginners: { phone: null } matches documents where phone is explicitly null AND documents where phone does not exist at all. This is because MongoDB considers an absent field to be implicitly null for equality comparisons.
If you want to distinguish between 'field is null' and 'field is missing', combine $exists with $eq: null. For most practical purposes, both cases mean 'no phone number provided' and you can treat them the same way.
// 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: Querying by BSON Type
MongoDB is schema-flexible, which means a field like price might be a Number in most documents but accidentally stored as a String in some legacy records. The $type operator lets you filter documents based on the BSON type of a field's value.
You can specify types by name (e.g., 'string', 'int', 'date') or by BSON type number (e.g., 2 for String, 16 for Int32). Using type names is more readable and preferred.
// 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' } });Common BSON Type Names
The most commonly used BSON type names for $type queries are:
'double'— 64-bit floating point number (JS default number)'string'— UTF-8 string'object'— embedded document (sub-object)'array'— array'binData'— binary data'objectId'— ObjectId'bool'— boolean'date'— Date'null'— null value'int'— 32-bit integer'long'— 64-bit integer'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 _idsThe 'number' Alias
A useful shortcut is the 'number' type alias, which matches any numeric BSON type: double, int, long, and decimal. This is equivalent to writing an array of all four numeric type names but much cleaner.
Use 'number' when you want to find all documents where a field is any kind of number, regardless of the specific precision. This is particularly useful for data quality checks where you want to identify fields that should be numeric but were accidentally stored as strings.
// '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) } }
);
});Using $exists in Schema Migrations
Schema migrations in MongoDB often involve adding new required fields to existing documents. $exists helps you find the documents that still need to be updated—or that have already been migrated.
A typical migration flow: first use { newField: { $exists: false } } to find all legacy documents, then add the new field with a default value. Check progress by counting how many documents still have $exists: false. This rolling migration approach works without downtime because MongoDB's flexible schema allows old and new document shapes to coexist temporarily.
// 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);
}Combining $exists and $type
You can combine $exists and $type in one query to find documents where a field exists AND has a specific type. This is useful for data quality audits where you want to identify documents with the field set to the wrong type versus documents where the field is simply absent.
For example, in a collection where age should be a number, you might want to separately count: (1) documents missing the age field entirely, (2) documents where age is a string (data entry error), and (3) documents where age is a valid number.
// 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: Cross-Field Comparisons
The $expr operator lets you use aggregation expressions inside a regular find() filter. This unlocks cross-field comparisons—something that is not possible with standard query operators.
For example: 'find orders where the discount is greater than 50% of the original price' requires comparing discount to a computed fraction of originalPrice—two fields in the same document. $expr enables these relationships using expression operators like $gt, $multiply, and $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'] }
});Indexing for $exists Queries
Queries using $exists have mixed index support. An index on a field does not help with { field: { $exists: false } }—if the field is absent, it is not in the index—so MongoDB must scan every document. However, { field: { $exists: true } } can use a sparse index: a sparse index only stores entries for documents that have the indexed field, enabling fast lookups of existing documents.
Use db.collection.createIndex({ field: 1 }, { sparse: true }) to create a sparse index when you frequently query for documents that have an optional field.
// 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 queriesData Quality Audit Workflow
A practical data quality audit workflow using and : first identify all distinct field patterns in your collection to spot inconsistencies, then categorize documents by their field types, and finally write targeted updateMany calls to normalize the data.
MongoDB's aggregation pipeline makes auditing easy: by of a field to count how many documents have each type. This gives you a clear picture of data quality issues before you start fixing them.
// 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!Quick Check
Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.
Lesson Recap
In this lesson you learned: $exists queries for the presence or absence of a field—a missing field and a null field are different and can be distinguished by combining $exists: true with $ne: null, $type filters by BSON type name (string, number, date, array, etc.) and is essential for data quality audits in schema-flexible collections, and sparse indexes efficiently support $exists: true queries on optional fields by only indexing documents that have the field. Next up we explore regex queries and pattern matching for flexible text searches.
자주 묻는 질문
“요소 연산자와 유형 검사” 강의는 무료인가요?
네 — “요소 연산자와 유형 검사” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“요소 연산자와 유형 검사”에서 뭘 배우나요?
$exists와 $type을 사용하여 필드의 존재 여부나 유형을 조회하고 선택적 데이터 또는 여러 유형이 섞인 데이터를 처리합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“요소 연산자와 유형 검사” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.