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

การค้นหา document

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

ตลอดบทเรียนนี้ให้สมมติว่า collection members มีคนที่เรา insert ไว้ก่อนหน้านี้อยู่แล้ว เราจะยิงคำถามที่แม่นยำขึ้นเรื่อย ๆ ใส่ collection นี้

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

db.members.findOne({ name: "Grace" })

document รายการเดียวที่ตรงกันกลับมาแบบเต็ม ๆ รวมถึง _id ด้วย:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d0",
"name": "Grace",
"joined": 2024,
"fines": 0
}

filter ที่ว่างเปล่าตรงกับทุกอย่าง นั่นคือวิธีที่คุณอ่าน collection ทั้งก้อน ใช้ find แล้วคุณจะได้ cursor ที่ครอบทั้ง collection:

db.members.find({})

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

db.members.find({ joined: 2023, fines: 0 })

ผลลัพธ์คือรายการของ document ที่ตรงกับทั้งสองเงื่อนไข:

[
{
"_id": "65f0b3d4e4b0a1c2e4b0a1d2",
"name": "Margaret",
"joined": 2023,
"fines": 0
}
]

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

db.members.find({ joined: 2023 }, { name: 1, _id: 0 })

ตอนนี้ผลลัพธ์แต่ละรายการมีเฉพาะ 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
ตัวเลือกBenefitCost
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 สำหรับตารางรายงาน

find คืนอะไร เมื่อเทียบกับ findOne?
ใน filter ที่มีสอง field เงื่อนไขถูกรวมกันอย่างไร?
คุณกัน _id ออกจาก projection ได้อย่างไร?