ข้ามไปยังเนื้อหา

การค้นหา document

การอ่านคือจุดที่ฐานข้อมูลพิสูจน์คุณค่า และ MongoDB ให้ประตูสองบานเข้าสู่ห้องเดียวกัน findOne ใช้เมื่อคุณคาดว่าจะได้ document เดียว — “สมาชิกชื่อ Grace คือใคร?” — และส่ง document นั้นมาหรือไม่ส่งอะไรเลย find ใช้เมื่ออาจได้หลายรายการ — “สมาชิกคนไหนเข้าร่วมในปี 2023?” — และส่ง cursor ที่คุณวนอ่านได้ ทั้งสองรับ filter ชนิดเดียวกัน คือ document ที่อธิบายว่าต้องการระเบียนไหน

ทุก output ด้านล่างจับมาจาก mongo 8.3.11, mongosh 2.11.1 และ Node driver 7.7.0 (แท็บ Python, Go และ Rust รันหนึ่งครั้งในโปรแกรม scratch บน server เดียวกัน) object id บนเครื่องของคุณจะต่างออกไป

แต่ละบทเรียน seed ข้อมูลของตัวเอง คุณจึงเปิดบทไหนก็ได้โดยลำพัง นี่คือสมาชิกห้องสมุดหกคน — รูปร่างเดียวกันตลอดคอร์ส:

mongosh/find-seed.js
db.members.insertMany([
{ name: "Ada", joined: 2023, fines: 0, city: "Cambridge", borrowed: ["Compilers"] },
{ name: "Grace", joined: 2024, fines: 0, city: "Arlington", borrowed: ["Compilers", "Algorithms"] },
{ name: "Linus", joined: 2023, fines: 2, city: "Helsinki", borrowed: [] },
{ name: "Margaret", joined: 2023, fines: 0, city: "Cambridge", borrowed: ["Algorithms"] },
{ name: "Edsger", joined: 2022, fines: 5, city: "Austin", borrowed: ["Compilers", "Algorithms", "Databases"] },
{ name: "Barbara", joined: 2025, fines: 1, city: "Boston", borrowed: [] },
]);
assert.strictEqual(db.members.countDocuments(), 6);

(สำหรับ Python, Go และ Rust ให้ insert หกรายการเดียวกันเหมือนบทก่อนหน้า)

ความต่างอยู่ที่รูปร่าง ไม่ใช่ไวยากรณ์ findOne คืน document ไม่เกินหนึ่งโดยตรง find คืน cursor — handle แบบ lazy ที่ driver วนดึง document เป็น batch — คุณจึงมักแปลงเป็น array (หรือ loop) เพื่อดูผลลัพธ์

mongosh/find-one.js
const member = db.members.findOne({ name: "Grace" });
console.log(member);
console.log(db.members.findOne({ name: "Nobody" })); // no match
assert.strictEqual(member.name, "Grace");

document ที่ตรงกันกลับมาเต็ม รวม _id และการไม่พบคือ null (ไม่ใช่ error) ใน shell และ Node:

{
_id: ObjectId('6abca853ca24543a2bc8573d'),
name: 'Grace',
joined: 2024,
fines: 0,
city: 'Arlington',
borrowed: [ 'Compilers', 'Algorithms' ]
}
null

แต่ละ driver รายงาน “ไม่มีสมาชิกคนนั้น” ต่างกันและสำคัญต่อการจัดการ error: Python คืน None, Rust คืน None ใน Option, Node คืน null — แต่ FindOne(...).Decode() ของ Go คืน error mongo: no documents in result (mongo.ErrNoDocuments) ซึ่งต้องถือเป็น “ไม่พบ” ไม่ใช่ความล้มเหลว

filter ว่างตรงกับทุกอย่าง นั่นคือวิธีอ่านทั้ง collection ด้วย find คุณได้ cursor ครอบทั้งหมด:

mongosh/find-all.js
const cursor = db.members.find({});
console.log(cursor.constructor.name); // a Cursor, not an array
const all = cursor.toArray();
console.log(all.map((m) => m.name));
assert.strictEqual(all.length, 6);
Cursor
[ 'Ada', 'Grace', 'Linus', 'Margaret', 'Edsger', 'Barbara' ]

“handle แบบ lazy” ไม่ใช่คำเปรียบเทียบ เราเปิด profiler บน database scratch, insert document เล็ก ๆ 1000 รายการ แล้วนับ round trip ไปยัง server ของการอ่านสี่แบบ:

mongosh/find-batches.js
db.big.insertMany(Array.from({ length: 1000 }, (_, i) => ({ i, pad: "x".repeat(50) })));
const trip = (label, fn) => {
db.setProfilingLevel(2); // profile every operation of THIS database only
const since = new Date();
const n = fn();
db.setProfilingLevel(0);
const ops = db.system.profile.find({ ts: { $gte: since }, ns: db.getName() + ".big" }).sort({ ts: 1 }).toArray();
console.log(label, "docs:", n, "| server round trips:", ops.map((o) => `${o.op}(${o.nreturned})`).join(" "));
return ops;
};
const a = trip("find().toArray() ", () => db.big.find().toArray().length);
const b = trip("find().batchSize(100) ", () => db.big.find().batchSize(100).toArray().length);
const c = trip("find().limit(5) ", () => db.big.find().limit(5).toArray().length);
const d = trip("findOne() ", () => (db.big.findOne() ? 1 : 0));
assert.strictEqual(a.length, 2);
assert.strictEqual(c.length, 1);
find().toArray() docs: 1000 | server round trips: query(101) getmore(899)
find().batchSize(100) docs: 1000 | server round trips: query(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(100) getmore(0)
find().limit(5) docs: 5 | server round trips: query(5)
findOne() docs: 1 | server round trips: query(1)

คำตอบแรกพก 101 document getMore ถัดไปพกที่เหลือ (ถึงเพดานขนาดคำตอบ); batchSize(100) บังคับ round trip สิบเอ็ดครั้ง; limit(5) และ findOne ใช้พอดีหนึ่งครั้ง นั่นคือเหตุผลที่ shell พิมพ์ 20 document แล้วตามด้วย Type "it" for more: it คือ shell ขอ batch ถัดไปจาก cursor

filter ที่ระบุ field และค่าหมายถึง “field นี้เท่ากับค่านี้” วางหลาย field ใน filter เดียว MongoDB ถือเป็น implicit AND — document ต้องตรงทุกเงื่อนไขจึงถูกคืน ที่นี่เราขอสมาชิกที่เข้าร่วมปี 2023 และ ไม่มีค่าปรับค้าง:

mongosh/find-equality.js
const matches = db.members.find({ joined: 2023, fines: 0 }, { _id: 0, name: 1 }).toArray();
console.log(matches);
console.log(db.members.find({ joined: 2099, fines: 0 }).toArray()); // a miss: empty array, no error
assert.strictEqual(matches.map((m) => m.name).join(), "Ada,Margaret");

สมาชิกสองคนตรงทั้งสองเงื่อนไข; filter ที่ไม่มีใครตรงคืน list ว่าง:

[ { name: 'Ada' }, { name: 'Margaret' } ]
[]

(Node พิมพ์ [ 'Ada', 'Margaret' ]; Python, Go และ Rust คืน 2 document เดียวกันเช่นกัน)

โดยค่าเริ่มต้นการอ่านคืนทุก field ของทุก document ที่ตรง projection ให้คุณขอเฉพาะ field ที่สนใจ ซึ่งลดข้อมูลที่ส่งผ่านสาย คุณส่ง document ที่สองโดย 1 หมายถึง “รวม field นี้” ที่นี่เราต้องการแค่ชื่อ และปิด _id อย่างชัดเจนด้วยการตั้งเป็น 0:

mongosh/find-projection.js
console.log(db.members.find({ joined: 2023 }, { name: 1, _id: 0 }).toArray());
console.log(db.members.find({}, { name: 1 }).limit(1).toArray()); // _id sneaks in
let err;
try { db.members.find({}, { name: 1, fines: 0 }).toArray(); } catch (e) { err = e; } // mixing include and exclude
console.log(err.name, err.code, err.message);
assert.strictEqual(err.code, 31254);

ตอนนี้แต่ละผลลัพธ์มีเฉพาะ field ที่ขอ — และกับดักสองข้อเป็นของจริง:

[ { name: 'Ada' }, { name: 'Linus' }, { name: 'Margaret' } ]
[ { _id: ObjectId('6abca853ca24543a2bc8573c'), name: 'Ada' } ]
MongoServerError 31254 Cannot do exclusion on field fines in inclusion projection

_id ถูกรวมเสมอเว้นแต่คุณปิดด้วย _id: 0 และการผสม inclusion (name: 1) กับ exclusion (fines: 0) ถูก server ปฏิเสธ (code 31254)

ใน Compass: query bar มีช่อง Filter และปุ่ม Find; drop-down Options เพิ่ม Project, Sort, Limit, Skip, Collation และ Max Time MS (เอกสาร Compass, mongodb.com/docs/compass/current/query/filter, เรียกอ่านเมื่อ 2026-09-30) สังเกตว่า filter bar ของ Compass รับทุก query operator ยกเว้น $text และ $expr

  • find คืน cursor ไม่ใช่ list ไม่มีอะไรถูกดึงนอกเหนือ batch แรกจนกว่าคุณจะวนอ่าน (หรือเรียก helper อย่าง toArray) findOne ก็คือ cursor ที่หยุดหลัง document แรก
  • field _id ถูกรวมโดยค่าเริ่มต้น ในทุก projection ถ้าไม่ต้องการ ให้ปิดอย่างชัดเจนด้วย _id: 0
  • อย่าผสม 1 และ 0 ใน projection เดียวกัน ยกเว้น _id เลือกระบุ field ที่ต้องการ หรือระบุ field ที่ต้องการทิ้ง — ไม่ใช่ทั้งสองอย่าง
  • filter ที่ไม่ตรงอะไรเลยคืน cursor ว่าง (หรือสำหรับ findOne คือ null/None; ใน Go เป็น error ErrNoDocuments) นั่นเป็นคำตอบปกติ ไม่ใช่ความล้มเหลว
  • filter เป็น object ธรรมดาในภาษาของคุณ ดังนั้น key ที่ซ้ำเก็บเฉพาะค่าสุดท้ายอย่างเงียบ ๆ (ดูด้านล่าง)

ขอผู้ช่วย AI ว่า “สมาชิกที่มียอดค้างเล็กน้อย มากกว่า 0 และน้อยกว่า 5” คุณอาจได้:

🐛 หา bug ในโค้ดนี้

// AI comment: "fines greater than 0 AND less than 5"
const owing = await db.collection("members")
.find({ fines: { $gt: 0 }, fines: { $lt: 5 } })
.toArray();
เฉลย

object literal ของ JavaScript ไม่มี key ซ้ำได้: fines ตัวที่สอง แทนที่ ตัวแรกอย่างเงียบ ๆ filter ที่ถึง server จึงเป็นแค่ { fines: { $lt: 5 } } บนสมาชิกหกคนของเรา มันคืน ห้า document รวม Ada, Grace และ Margaret ที่ไม่มีค้าง:

[ 'Ada 0', 'Grace 0', 'Linus 2', 'Margaret 0', 'Barbara 1' ] <- find({ fines: {$gt: 0}, fines: {$lt: 5} })
[ 'Linus 2', 'Barbara 1' ] <- find({ fines: {$gt: 0, $lt: 5} })

ใส่ทั้งสอง operator ใน sub-document เดียว หรือใช้ $and ที่ชัดเจน fence นี้พิสูจน์ว่าการแก้คืนพอดีสองคนที่ค้างเล็กน้อย:

mongosh/find-range-fix.js
db.members.drop();
db.members.insertMany([
{ name: "Ada", fines: 0 }, { name: "Grace", fines: 0 }, { name: "Linus", fines: 2 },
{ name: "Margaret", fines: 0 }, { name: "Edsger", fines: 5 }, { name: "Barbara", fines: 1 },
]);
const wrong = db.members.find({ fines: { $gt: 0 }, fines: { $lt: 5 } }, { _id: 0, name: 1, fines: 1 }).toArray();
const right = db.members.find({ fines: { $gt: 0, $lt: 5 } }, { _id: 0, name: 1, fines: 1 }).toArray();
const explicit = db.members.find({ $and: [{ fines: { $gt: 0 } }, { fines: { $lt: 5 } }] }, { _id: 0, name: 1 }).toArray();
console.log(wrong.map((d) => `${d.name} ${d.fines}`), right.map((d) => `${d.name} ${d.fines}`), explicit.length);
assert.strictEqual(wrong.length, 5);
assert.strictEqual(right.length, 2);
assert.strictEqual(explicit.length, 2);
[ 'Ada 0', 'Grace 0', 'Linus 2', 'Margaret 0', 'Barbara 1' ] [ 'Linus 2', 'Barbara 1' ] 2

การอ่านคือ filter แล้วจัดรูป: filter (document) ตัดสิน document ไหน ที่ตรง, projection ตัดสิน field ไหน ที่กลับมา และ cursor ตัดสินว่าต้อง กี่ round trip find ให้ cursor; findOne คือ find().limit(1) ที่แกะห่อแล้ว เก็บ filter ไว้ฝั่ง server และเล็ก คุณก็เก็บสายให้เล็ก

ตัวเลือกBenefitCost
filter แบบ equality ธรรมดา (field: value)เขียนและอ่านง่าย ใช้ index ตรง ๆ และมีประสิทธิภาพตอบได้เฉพาะคำถามแบบ “เท่ากับ”
filter หลาย field แบบ implicit ANDกรองได้คมและแม่นกว่าโดยไม่ต้องใช้ operator เพิ่มยิ่งมี field มาก ยิ่งต้องมี compound index ที่ลำดับ field ตรงกัน ไม่เช่นนั้น query ช้าลง
filter ที่ใช้ operator ซับซ้อน ($gt, $in, $regex ฯลฯ)ยืดหยุ่นมาก ตอบคำถามช่วงหรือรูปแบบได้ออกแบบ index ยากกว่า; operator บางตัว (เช่น $regex ที่ไม่ anchor) ใช้ index ได้ไม่ดี
batch ค่าเริ่มต้น (toArray)2 round trip สำหรับ 1000 document (101 + 899)ผลลัพธ์ทั้งหมดอยู่ในหน่วยความจำ; ผลใหญ่มากควรวน cursor แทน
batchSize เล็กหน่วยความจำต่อ batch มีขอบเขต11 round trip สำหรับ 1000 document เดียวกัน
  • “find คืนผลลัพธ์ทันทีที่เรียก” — มันคืน cursor แบบ lazy การลืม toArray()/วนอ่านไม่ทำให้เกิด error แต่ก็ไม่ได้ข้อมูล
  • “find({}) แล้วกรองในโค้ด client ก็ได้” — ส่ง filter ไปให้ MongoDB กรองระดับ query เพื่อส่งข้อมูลผ่านสายน้อยลงและให้ index ช่วยได้
  • “find กับ findOne ใช้แทนกันได้” — findOne คืน document ธรรมดา (หรือ null); find().toArray()[0] ดึงทั้ง batch เพื่อใช้ document เดียว
  • “{ a: {$gt: 0}, a: {$lt: 5} } คือช่วง” — key ที่สองแทนที่ตัวแรก; การรันจริงคืนห้า document แทนสอง
  • “การไม่พบคือ error” — ไม่ใช่ใน shell, Node, Python หรือ Rust; ใน Go Decode คืน mongo: no documents in result

ตัวอย่างจากของจริง

เอกสาร query bar ของ Compass แสดงโมเดล filter-document เดียวกันใน GUI: ช่อง Filter รับทุก query operator ยกเว้น $text และ $expr โดยมี Project, Sort และ Limit ใต้ Options (Compass: filter documents, เรียกอ่านเมื่อ 2026-09-30)

แท็บ Python/Go/Rust สะท้อนพฤติกรรมที่พิสูจน์แล้วของ shell/Node; แต่ละแท็บถูกรันหนึ่งครั้งในโปรแกรม scratch กับ server เดียวกัน (output ของ Go, Python และ Rust ตรงกัน: พบ Grace, 6 document, ตรง equality 2 รายการ, ชื่อที่ project 3 รายการ) แต่ไม่ถูกรันโดยตัวตรวจอัตโนมัติของคอร์ส

find คืนอะไร เมื่อเทียบกับ findOne?
ใน filter ที่มีสอง field เงื่อนไขถูกรวมกันอย่างไร?
จะกัน _id ออกจาก projection ได้อย่างไร?
find({ fines: { $gt: 0 }, fines: { $lt: 5 } }) ส่งอะไรไปยัง server จริง ๆ (JavaScript)?
คุณเรียก db.big.find().batchSize(100).toArray() บน 1000 document ประมาณกี่ round trip ไปยัง server?