การค้นหา document
การอ่านคือจุดที่ฐานข้อมูลพิสูจน์คุณค่าตัวเอง และ MongoDB มอบประตูสองบานเข้าสู่ห้องเดียวกันให้คุณ findOne ใช้สำหรับเมื่อคุณคาดหวัง document เพียงรายการเดียว — “สมาชิกชื่อ Grace คือใคร?” — แล้วคุณจะได้ document รายการนั้นกลับมา หรือไม่ได้อะไรเลย find ใช้สำหรับเมื่อคุณอาจได้หลายรายการ — “สมาชิกคนใดบ้างที่เข้าร่วมในปี 2023?” — แล้วคุณจะได้ cursor กลับมาไว้วนซ้ำ ทั้งคู่รับ filter ชนิดเดียวกัน นั่นคือ document ที่อธิบายว่าคุณต้องการระเบียนใด
ตลอดบทเรียนนี้ให้สมมติว่า collection members มีคนที่เรา insert ไว้ก่อนหน้านี้อยู่แล้ว เราจะยิงคำถามที่แม่นยำขึ้นเรื่อย ๆ ใส่ collection นี้
findOne เทียบกับ find
หัวข้อที่มีชื่อว่า “findOne เทียบกับ find”ความแตกต่างคือรูปทรง ไม่ใช่ syntax findOne คืน document อย่างมากที่สุดหนึ่งรายการโดยตรง find คืน cursor — handle แบบ lazy ที่ driver วนซ้ำเพื่อดึง document มาทีละชุด — ปกติคุณจึงแปลงเป็น array (หรือวนลูปครอบ) เพื่อดูผลลัพธ์
db.members.findOne({ name: "Grace" })const member = await db.collection("members").findOne({ name: "Grace" });console.log(member);member = db.members.find_one({"name": "Grace"})print(member)var member bson.Merr := coll.FindOne(ctx, bson.M{"name": "Grace"}).Decode(&member)if err != nil { return err}fmt.Println(member)let member = coll.find_one(doc! { "name": "Grace" }).await?;println!("{:?}", member);document รายการเดียวที่ตรงกันกลับมาแบบเต็ม ๆ รวมถึง _id ด้วย:
{ "_id": "65f0b3d4e4b0a1c2e4b0a1d0", "name": "Grace", "joined": 2024, "fines": 0}filter ที่ว่างเปล่าตรงกับทุกอย่าง นั่นคือวิธีที่คุณอ่าน collection ทั้งก้อน ใช้ find แล้วคุณจะได้ cursor ที่ครอบทั้ง collection:
db.members.find({})const all = await db.collection("members").find({}).toArray();console.log(all);all_members = list(db.members.find({}))print(all_members)cur, err := coll.Find(ctx, bson.M{})if err != nil { return err}var all []bson.Mif err := cur.All(ctx, &all); err != nil { return err}fmt.Println(all)use futures::stream::TryStreamExt;
let mut cursor = coll.find(doc! {}).await?;while let Some(member) = cursor.try_next().await? { println!("{:?}", member);}filter แบบ equality
หัวข้อที่มีชื่อว่า “filter แบบ equality”filter ที่ระบุ field และค่าหมายความว่า “field นี้เท่ากับค่านี้” วาง field หลาย ๆ ตัวซ้อนกันใน filter เดียว แล้ว MongoDB จะมองว่าเป็น implicit AND — document ต้อง match ทุกเงื่อนไขถึงจะได้กลับมา ในที่นี้เราขอสมาชิกที่เข้าร่วมในปี 2023 และ ไม่มีค่าปรับค้างชำระ:
db.members.find({ joined: 2023, fines: 0 })const matches = await db .collection("members") .find({ joined: 2023, fines: 0 }) .toArray();console.log(matches);matches = list(db.members.find({"joined": 2023, "fines": 0}))print(matches)cur, err := coll.Find(ctx, bson.M{"joined": 2023, "fines": 0})if err != nil { return err}var matches []bson.Mif err := cur.All(ctx, &matches); err != nil { return err}fmt.Println(matches)use futures::stream::TryStreamExt;
let mut cursor = coll.find(doc! { "joined": 2023, "fines": 0 }).await?;let matches: Vec<_> = cursor.try_collect().await?;println!("{:?}", matches);ผลลัพธ์คือรายการของ document ที่ตรงกับทั้งสองเงื่อนไข:
[ { "_id": "65f0b3d4e4b0a1c2e4b0a1d2", "name": "Margaret", "joined": 2023, "fines": 0 }]การได้ลิ้มลอง projection เป็นครั้งแรก
หัวข้อที่มีชื่อว่า “การได้ลิ้มลอง projection เป็นครั้งแรก”โดยปริยายการอ่านจะคืนทุก field ของทุก document ที่ตรงกัน projection ให้คุณขอเฉพาะ field ที่คุณสนใจ ซึ่งช่วยลดข้อมูลที่ส่งผ่านสายลง คุณส่ง document ตัวที่สองเข้าไปโดยที่ 1 หมายถึง “รวม field นี้” ในที่นี้เราต้องการเพียงชื่อ และเราปิด _id อย่างชัดเจนด้วยการกำหนดให้เป็น 0:
db.members.find({ joined: 2023 }, { name: 1, _id: 0 })const names = await db .collection("members") .find({ joined: 2023 }, { projection: { name: 1, _id: 0 } }) .toArray();console.log(names);names = list(db.members.find({"joined": 2023}, {"name": 1, "_id": 0}))print(names)opts := options.Find().SetProjection(bson.M{"name": 1, "_id": 0})cur, err := coll.Find(ctx, bson.M{"joined": 2023}, opts)if err != nil { return err}var names []bson.Mif err := cur.All(ctx, &names); err != nil { return err}fmt.Println(names)use futures::stream::TryStreamExt;use mongodb::options::FindOptions;
let opts = FindOptions::builder() .projection(doc! { "name": 1, "_id": 0 }) .build();let mut cursor = coll.find(doc! { "joined": 2023 }).with_options(opts).await?;let names: Vec<_> = cursor.try_collect().await?;println!("{:?}", names);ตอนนี้ผลลัพธ์แต่ละรายการมีเฉพาะ field ที่ร้องขอเท่านั้น:
[{ "name": "Margaret" }]ใน Compass: พิมพ์ filter ลงในแถบ Filter ที่ด้านบนของแท็บ Documents แล้วกด Find เปิดแผง Options เพื่อกรอก projection โดยไม่ต้องเขียนการเรียกด้วยมือ
เคล็ดลับและจุดที่ต้องระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและจุดที่ต้องระวัง”findคืน cursor ไม่ใช่ list ยังไม่มีอะไรถูกดึงมาจนกว่าคุณจะวนซ้ำ (หรือเรียก helper อย่างtoArray)findOneแท้จริงแล้วคือ cursor ที่หยุดหลังจาก document แรก- field
_idถูก รวมไว้โดยปริยาย ในทุก projection ถ้าไม่ต้องการ ต้องปิดเองอย่างชัดเจนด้วย_id: 0 - อย่าผสม
1และ0ใน projection เดียวกัน ยกเว้นสำหรับ_idให้เลือกระหว่างการระบุ field ที่คุณต้องการ หรือระบุ field ที่คุณต้องการตัดออก — ไม่ใช่ทั้งสองอย่าง - filter ที่ไม่ตรงกับอะไรเลยคืน cursor ที่ว่างเปล่า (หรือสำหรับ
findOneคือผลลัพธ์ที่เป็น null) นั่นเป็นคำตอบปกติ ไม่ใช่ error
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
equality filter ธรรมดา (field: value) | เขียนง่าย อ่านง่าย ใช้ index ตรง ๆ ได้อย่างมีประสิทธิภาพ | ตอบคำถามได้จำกัด แค่ “เท่ากับ” เท่านั้น |
| filter หลาย field แบบ implicit AND | คมชัดขึ้น กรองได้แม่นกว่าโดยไม่ต้องเขียน operator เพิ่ม | field ยิ่งเยอะ ยิ่งต้องมี compound index ที่ตรงกับลำดับ field ไม่งั้น query ช้า |
filter ที่ใช้ operator ซับซ้อน ($gt, $in, $regex ฯลฯ) | ยืดหยุ่นสูง ตอบคำถามเชิงช่วงหรือ pattern ได้ | ออกแบบ index ให้ครอบคลุมยากขึ้น บาง operator (เช่น $regex ที่ไม่ขึ้นต้นด้วย anchor) ใช้ index ไม่ได้เต็มที่หรือใช้ไม่ได้เลย |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- คิดว่า
findคืนผลลัพธ์ทันทีที่เรียก — จริง ๆ แล้วได้แค่ cursor แบบ lazy เท่านั้น ถ้าลืมtoArray()/วนลูป ก็จะไม่เห็น error แต่ก็ไม่เห็นข้อมูลเช่นกัน - ใช้
find({})แล้วดึงทั้ง collection มา filter ในแอปฝั่ง client — ควรส่ง filter ที่ต้องการไปให้ MongoDB กรองให้ตั้งแต่ query เพื่อลดข้อมูลที่ต้องส่งผ่านสาย และให้ index ช่วยงานได้ - สับสนระหว่าง
findกับfindOneเมื่อคาดหวัง document เดียว — เผลอเรียกfindแล้วอ่านผลลัพธ์ตัวแรกจาก array ทั้งที่findOneตรงจุดประสงค์กว่าและคืนค่า null ตรงไปตรงมาเมื่อไม่พบ
💡 ตัวอย่างจากของจริง
หน้าค้นหาสินค้าของ e-commerce — ผู้ใช้กรองด้วยหลาย field พร้อมกัน (หมวดหมู่ + ช่วงราคา + สถานะมีสต็อก) ระบบประกอบ filter document ที่มีหลาย field และหลาย operator เข้าด้วยกัน แล้วพึ่ง compound index ให้ query ยังเร็วแม้ collection มีสินค้าเป็นล้านรายการ
Dashboard แอดมิน — ใช้
findOne({ email })ตรวจสอบผู้ใช้ตอน login และfind({ status: "active" })ดึงรายชื่อผู้ใช้ที่ยัง active สำหรับตารางรายงาน