ใช้ $where กับ JavaScript Function สำหรับ Query แบบ Dynamic
เมื่อโครงสร้างข้อมูลมีฟิลด์ซ้อนที่ไม่คงที่ (dynamic keys) ใช้ $where พร้อม JavaScript function วนลูปตรวจสอบทุกระดับ
ตัวอย่างข้อมูล (2 ระดับ: FirstPosition, SecondPosition)
> db.demo468.insertOne({
"_id": new ObjectId(),
"FirstPosition": { "StudentName": "Chris", "StudentAge": 23 },
"SecondPosition": { "StudentName": "David", "StudentAge": 20 }
});
> db.demo468.insertOne({
"_id": new ObjectId(),
"FirstPosition": { "StudentName": "Carol", "StudentAge": 21 },
"SecondPosition": { "StudentName": "John", "StudentAge": 22 }
});
Query หาเอกสารที่มี StudentName = "John" ในระดับไหนก็ได้
> db.demo468.find({
$where: function() {
for (var i in this) {
if (this[i]["StudentName"] == "John") {
return true;
}
}
return false;
}
});
ผลลัพธ์
{ "_id": ObjectId(...), "FirstPosition": { "StudentName": "Carol", "StudentAge": 21 }, "SecondPosition": { "StudentName": "John", "StudentAge": 22 } }
ข้อควรระวังกับ $where
- ช้า - ต้อง evaluate JavaScript ทุกเอกสาร ไม่ใช้ index
- Security risk - JavaScript injection หากรับ input จาก user
- Deprecated trend - แนะนำให้ redesign schema หรือใช้ aggregation แทน
ทางเลือกที่ดีกว่า (Schema Redesign)
// แปลงเป็นอาร์เรย์ positions
{ "positions": [
{ "type": "FirstPosition", "StudentName": "Chris", "StudentAge": 23 },
{ "type": "SecondPosition", "StudentName": "David", "StudentAge": 20 }
]}
// แล้ว query ด้วย $elemMatch (เร็ว, ใช้ index ได้)
db.collection.find({ "positions": { $elemMatch: { "StudentName": "John" } } });
Aggregation Alternative (MongoDB 4.4+)
db.demo468.aggregate([
{ $project: {
matched: { $gt: [{ $size: { $filter: { input: { $objectToArray: "$$ROOT" }, as: "kv", cond: { $eq: ["$$kv.v.StudentName", "John"] } } } }, 0] }
}},
{ $match: { matched: true } }
]);