CRUD ใน MongoDB
ทุกฐานข้อมูลสรุปลงมาเหลือสี่สิ่งที่คุณทำกับข้อมูลได้ คือ ใส่ระเบียนใหม่เข้าไป อ่านกลับออกมา แก้ไข และลบทิ้ง สี่กริยานี้ย่อรวมกันเป็น CRUD — Create, Read, Update, Delete
ใน MongoDB “ระเบียน” เหล่านั้นคือ document หน้าตาคล้าย JSON ที่อยู่ใน collection และกริยาทั้งสี่จับคู่กับชุด method เล็ก ๆ ที่คาดเดาได้ หน้าตาแทบไม่ต่างกันเลย ไม่ว่าคุณจะพิมพ์ในเชลล์ หรือเรียกผ่าน driver ของ Node.js, Python, Go หรือ Rust
โมดูลนี้คือหัวใจของคอร์ส เมื่อจบโมดูลคุณจะโหลด document เข้า collection ดึงกลับออกมาด้วย filter แก้ field อย่างแม่นยำโดยไม่พังส่วนที่เหลือของ document ลบเฉพาะสิ่งที่ตั้งใจจะลบ และรวมการเขียนหลายครั้งให้เหลือ round trip เดียวได้ ข้อมูลตัวอย่างเราจงใจให้เล็กและเป็นเรื่องสมมติ — สมาชิกห้องสมุดไม่กี่คนกับหนังสือที่ยืมไป — เพื่อให้โฟกัสอยู่ที่ตัว operation
flowchart LR
App["Your app or mongosh"] -->|"insert / find / update / delete"| Driver["Driver or shell"]
Driver --> Coll["Collection: members"]
Coll --> D1["{ _id, name, fines }"]
Coll --> D2["{ _id, name, fines }"]
Coll --> D3["{ _id, name, fines }"] กลุ่มการทำงานทั้งสี่
หัวข้อที่มีชื่อว่า “กลุ่มการทำงานทั้งสี่”แต่ละกลุ่มมี method แบบเอกพจน์ที่แตะ document ได้มากที่สุดหนึ่งรายการ และ method แบบพหูพจน์ที่แตะได้หลายรายการ ทั้งหมดนี้คือคำศัพท์ที่คุณจะได้ใช้ตลอดทั้งโมดูล:
- Create —
insertOneเพิ่ม document ทีละรายการ,insertManyเพิ่มทีละ array - Read —
findOneคืน document แรกที่ตรงกัน (หรือไม่คืนอะไรเลย),findคืน cursor ครอบทุกรายการที่ตรงกัน - Update —
updateOneเปลี่ยนรายการแรกที่ตรงกัน,updateManyเปลี่ยนทุกรายการที่ตรงกัน ทั้งคู่ใช้ update operator อย่าง$setและ$inc - Delete —
deleteOneลบรายการแรกที่ตรงกัน,deleteManyลบทุกรายการที่ตรงกัน
รูปแบบเดียวกันนี้ปรากฏในทั้งห้าแท็บที่คุณจะเห็นตลอดโมดูล สังเกตว่าโค้ด insert อ่านแล้วแทบเหมือนกันหมด ทั้งในเชลล์และในทุก driver:
db.members.insertOne({ name: "Ada", fines: 0 })await db.collection("members").insertOne({ name: "Ada", fines: 0 });db.members.insert_one({"name": "Ada", "fines": 0})coll.InsertOne(ctx, bson.M{"name": "Ada", "fines": 0})coll.insert_one(doc! { "name": "Ada", "fines": 0 }).await?;หลัง insert เสร็จ document ที่เก็บอยู่จะหน้าตาแบบนี้ — สังเกต _id ที่ MongoDB เติมให้:
{ "_id": "65f0a1c2e4b0a1c2e4b0a1c2", "name": "Ada", "fines": 0 }ใน Compass: ทั้งสี่กลุ่มนี้จับคู่กับ GUI ตรง ๆ — Compass แสดง document ในแท็บ Documents คุณ insert ได้ด้วยปุ่มสีเขียว ADD DATA แก้ field แบบ inline ได้ และลบ document ด้วยไอคอนถังขยะที่โผล่มาตอนเอาเมาส์ไปวาง
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”บทเรียนถัดไปจะหยิบแต่ละกลุ่มมาทีละกลุ่ม แล้วปิดท้ายด้วยสองท่าไม้ตายที่รวมหลายกลุ่มเข้าด้วยกัน:
- Insert — การเพิ่ม document ทีละรายการหรือทีละหลายรายการ และ
_idอัตโนมัติ - Read —
findเทียบกับfindOne, filter แบบ equality และแบบหลาย field พร้อมแตะ projection เป็นครั้งแรก - Update —
$set,$inc,$unset,$push,$pullและการอ่านจำนวนที่ตรงกัน (match count) ที่การเขียนคืนกลับมา - Delete — การลบ document อย่างแม่นยำ บวกกับรูปแบบ soft-delete
- Upsert และ bulk write — “insert ถ้ายังไม่มี ไม่อย่างนั้นก็ update” และการยัดหลาย operation ให้เหลือ round trip เดียวไป server
เคล็ดลับและจุดที่ต้องระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและจุดที่ต้องระวัง”- method แบบเอกพจน์ (
insertOne,findOne,updateOne,deleteOne) ไม่มีทางกระทบ document เกินหนึ่งรายการ แม้จะมีหลายรายการตรงกัน — ใช้เป็นราวกันตกได้ดี - method แบบเขียนจะคืน acknowledgement ที่บอกว่าเกิดอะไรขึ้น (จำนวนนับ, id ที่ insert เข้าไป) อ่านผลลัพธ์ตรงนี้คือวิธียืนยันว่า operation ทำตามที่คุณตั้งใจจริง
- filter ที่ไม่ตรงกับอะไรเลยไม่ถือเป็น error ถ้า update หรือ delete ไม่ตรงกับ document สักรายการ ผลลัพธ์ก็แค่รายงานว่าเปลี่ยนไป 0 รายการ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| method แบบเอกพจน์ (insertOne/updateOne/deleteOne) | ควบคุมแม่นยำ กระทบ document เดียว คาดเดาผลได้ง่าย | ต้องเรียกซ้ำหลายครั้งถ้าต้องจัดการหลาย document ทำให้ round trip บ่อย |
| method แบบพหูพจน์ (insertMany/updateMany/deleteMany) | จัดการหลาย document ในการเรียกเดียว เร็วกว่ามาก | filter หรือ array ที่ผิดพลาดกระทบวงกว้างกว่าที่ตั้งใจ |
| hard delete | คืนพื้นที่จัดเก็บทันที เรียบง่าย | ลบถาวร ไม่มี audit trail |
| soft delete (update flag) | กู้คืนได้ มี audit trail | ต้องกรอง flag ในทุก query ต่อจากนี้ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เขียนโค้ด insert วนลูปทีละ document — ควรใช้
insertManyส่งเป็น batch เดียว ลดจำนวน round trip ไป server - เรียก
updateMany/deleteManyโดยไม่ตรวจ filter ให้ดีก่อน — ลืมใส่ filter หรือใส่ filter กว้างเกินไป ทำให้กระทบ document ทั้ง collection โดยไม่ตั้งใจ - ไม่ตรวจผลลัพธ์ (acknowledgement) หลังเขียน — เห็นแค่ว่าไม่มี error โยนขึ้นมาแล้วสรุปว่าสำเร็จ ทั้งที่
matchedCount/modifiedCount/deletedCountอาจเป็น 0
💡 ตัวอย่างจากของจริง
ระบบสั่งซื้อ e-commerce — เมื่อลูกค้ากด checkout ระบบ
insertOneorder ใหม่ ตามด้วยupdateManyลด stock ของสินค้าในตะกร้าทุกชิ้น แล้วสุดท้ายdeleteManyล้างตะกร้าที่ค้างอยู่เกิน 30 วัน ทั้งหมดคือ CRUD สี่กลุ่มทำงานร่วมกันใน pipeline เดียวระบบ session ของเว็บแอป — ใช้
findOneตรวจ session ทุก request,updateOneต่ออายุ TTL, และdeleteOneเมื่อ logout