MongoDB CRUD Operations — คู่มือจัดการข้อมูลฉบับสมบูรณ์

การจัดการข้อมูลในฐานข้อมูลต้องอาศัยปฏิบัติการพื้นฐาน 4 อย่าง คือ Create (สร้าง) Read (อ่าน) Update (แก้ไข) และ Delete (ลบ) ซึ่งรวมเรียกว่า CRUD Operations ใน MongoDB การทำ CRUD จะใช้คำสั่ง mongosh หรือ Driver ของภาษาโปรแกรมต่าง ๆ โดยทำงานกับข้อมูลในรูปแบบ Document (BSON) แทนการใช้ SQL

บทความนี้จะแนะนำคำสั่ง CRUD ทั้งหมดของ MongoDB ตั้งแต่การเพิ่ม Document การค้นหาด้วย Query Operators การแก้ไขข้อมูลด้วย Update Operators ไปจนถึงการลบข้อมูล พร้อมตัวอย่างที่ใช้งานได้จริง

Create — เพิ่ม Document

การเพิ่มข้อมูลใหม่เข้า Collection ทำได้ 2 คำสั่งหลัก คือ insertOne สำหรับเพิ่มทีละ 1 Document และ insertMany สำหรับเพิ่มหลาย Document พร้อมกัน ถ้า Collection ยังไม่มีอยู่ ระบบจะสร้างให้อัตโนมัติ

insertOne

// เพิ่ม Document เดียว
db.products.insertOne({
  name: "Cloud VPS M",
  cpu: 2,
  ram: 4,
  storage: 80,
  price: 590,
  active: true,
  createdAt: new Date()
})

// ผลลัพธ์
// {
//   acknowledged: true,
//   insertedId: ObjectId("665a1b2c3d4e5f6a7b8c9d0e")
// }

ถ้าไม่ระบุ _id ระบบจะสร้าง ObjectId ให้อัตโนมัติ แต่ถ้าต้องการกำหนดเองก็สามารถใส่ _id ได้โดยตรง โดยค่าต้องไม่ซ้ำกับ Document อื่นใน Collection เดียวกัน

insertMany

// เพิ่มหลาย Document พร้อมกัน
db.products.insertMany([
  { name: "Cloud VPS S", cpu: 1, ram: 2, storage: 40, price: 290 },
  { name: "Cloud VPS L", cpu: 4, ram: 8, storage: 160, price: 1090 },
  { name: "Cloud VPS XL", cpu: 8, ram: 16, storage: 320, price: 2090 }
])

// ผลลัพธ์
// {
//   acknowledged: true,
//   insertedIds: {
//     '0': ObjectId("..."),
//     '1': ObjectId("..."),
//     '2': ObjectId("...")
//   }
// }

โดย Default ถ้า Document ตัวใดตัวหนึ่งเกิด Error (เช่น _id ซ้ำ) ระบบจะหยุดทันทีและ Document ที่เหลือจะไม่ถูกเพิ่ม ถ้าต้องการให้เพิ่มต่อแม้มี Error ให้ตั้ง ordered: false

// Unordered insert — ข้าม Document ที่ Error แล้วทำต่อ
db.products.insertMany(
  [
    { _id: 1, name: "Product A" },
    { _id: 1, name: "Product B" },  // ซ้ำ — ข้ามไป
    { _id: 2, name: "Product C" }   // ยังถูกเพิ่มได้
  ],
  { ordered: false }
)

Read — ค้นหา Document

การอ่านข้อมูลเป็นปฏิบัติการที่ใช้บ่อยที่สุด MongoDB มีคำสั่ง find สำหรับค้นหาหลาย Document และ findOne สำหรับค้นหา Document เดียว ความสามารถที่แตกต่างจากฐานข้อมูลเชิงสัมพันธ์คือการ Query ลงไปในโครงสร้าง Document ที่ซ้อนกันได้โดยตรง

find และ findOne

// ค้นหาทุก Document
db.products.find()

// ค้นหา Document เดียว
db.products.findOne({ name: "Cloud VPS M" })

// ค้นหาแบบมีเงื่อนไข
db.products.find({ active: true })

// จำกัด Field ที่แสดง (Projection)
db.products.find(
  { active: true },
  { name: 1, price: 1, _id: 0 }
)
// แสดงเฉพาะ name และ price ไม่แสดง _id

Comparison Operators

ตัวดำเนินการเปรียบเทียบช่วยให้ค้นหาข้อมูลได้ละเอียดยิ่งขึ้น โดยใช้ $ นำหน้าชื่อ Operator

// $eq — เท่ากับ (เหมือนไม่ใส่ Operator)
db.products.find({ price: { $eq: 590 } })

// $ne — ไม่เท่ากับ
db.products.find({ active: { $ne: false } })

// $gt, $gte — มากกว่า, มากกว่าหรือเท่ากับ
db.products.find({ price: { $gt: 500 } })
db.products.find({ ram: { $gte: 4 } })

// $lt, $lte — น้อยกว่า, น้อยกว่าหรือเท่ากับ
db.products.find({ price: { $lt: 1000 } })

// $in — ค่าตรงกับรายการใดรายการหนึ่ง
db.products.find({ cpu: { $in: [2, 4] } })

// $nin — ค่าไม่ตรงกับรายการใดเลย
db.products.find({ cpu: { $nin: [1, 2] } })

// รวมหลายเงื่อนไข — ราคา 500-1500
db.products.find({ price: { $gte: 500, $lte: 1500 } })

Logical Operators

// $and — ทุกเงื่อนไขต้องเป็นจริง (Default เมื่อใส่หลาย Field)
db.products.find({
  $and: [
    { price: { $gte: 500 } },
    { price: { $lte: 1500 } },
    { active: true }
  ]
})

// $or — เงื่อนไขใดเงื่อนไขหนึ่งเป็นจริง
db.products.find({
  $or: [
    { cpu: { $gte: 4 } },
    { ram: { $gte: 8 } }
  ]
})

// $not — กลับเงื่อนไข
db.products.find({
  price: { $not: { $gt: 1000 } }
})

// $nor — ไม่ตรงกับเงื่อนไขใดเลย
db.products.find({
  $nor: [
    { active: false },
    { price: { $gt: 2000 } }
  ]
})

Element และ Evaluation Operators

// $exists — ตรวจว่ามี Field นั้นหรือไม่
db.products.find({ discount: { $exists: true } })

// $type — ตรวจประเภทข้อมูล
db.products.find({ price: { $type: "number" } })

// $regex — ค้นหาด้วย Regular Expression
db.products.find({ name: { $regex: /cloud/i } })

// $expr — ใช้ Aggregation Expression ใน Query
db.orders.find({
  $expr: { $gt: ["$total", "$budget"] }
})

Query บน Nested Document และ Array

// Dot Notation — Query ลงไปใน Nested Document
db.servers.find({ "specs.ram": { $gte: 8 } })

// ตัวอย่างข้อมูล:
// { name: "Web Server", specs: { cpu: 4, ram: 8, disk: 160 } }

// Array — ค้นหา Document ที่ Array มีค่าที่ต้องการ
db.servers.find({ tags: "production" })
// ตัวอย่างข้อมูล: { name: "Web Server", tags: ["production", "web", "nginx"] }

// $elemMatch — เงื่อนไขหลายข้อบน Element เดียวกันใน Array
db.servers.find({
  metrics: {
    $elemMatch: { type: "cpu", value: { $gt: 80 } }
  }
})

// $size — ค้นหาตามจำนวนสมาชิกใน Array
db.servers.find({ tags: { $size: 3 } })

// $all — Array ต้องมีค่าทั้งหมดที่ระบุ
db.servers.find({ tags: { $all: ["production", "web"] } })

Cursor Methods — จัดการผลลัพธ์

// sort — เรียงลำดับ (1 = น้อยไปมาก, -1 = มากไปน้อย)
db.products.find().sort({ price: 1 })

// limit — จำกัดจำนวนผลลัพธ์
db.products.find().limit(10)

// skip — ข้ามผลลัพธ์ (ใช้สำหรับ Pagination)
db.products.find().skip(20).limit(10)  // หน้าที่ 3 (แสดงหน้าละ 10)

// count — นับจำนวน Document
db.products.countDocuments({ active: true })

// distinct — ดึงค่าที่ไม่ซ้ำ
db.products.distinct("cpu")

Update — แก้ไข Document

การแก้ไขข้อมูลใน Collection ทำได้หลายรูปแบบ ตั้งแต่การอัพเดต Field บางตัว การแทนที่ Document ทั้งตัว ไปจนถึงการ Upsert (อัพเดตถ้ามี หรือสร้างใหม่ถ้าไม่มี)

updateOne และ updateMany

// updateOne — แก้ไข Document แรกที่ตรงเงื่อนไข
db.products.updateOne(
  { name: "Cloud VPS M" },           // Filter
  { $set: { price: 550, active: true } }  // Update
)

// updateMany — แก้ไขทุก Document ที่ตรงเงื่อนไข
db.products.updateMany(
  { active: false },
  { $set: { active: true, updatedAt: new Date() } }
)

// ผลลัพธ์
// {
//   acknowledged: true,
//   matchedCount: 5,
//   modifiedCount: 5
// }

Update Operators ที่ใช้บ่อย

// $set — กำหนดค่า Field (สร้างใหม่ถ้ายังไม่มี)
db.products.updateOne(
  { name: "Cloud VPS M" },
  { $set: { "specs.bandwidth": "unlimited" } }
)

// $unset — ลบ Field ออก
db.products.updateOne(
  { name: "Cloud VPS M" },
  { $unset: { discount: "" } }
)

// $inc — เพิ่ม/ลดค่าตัวเลข
db.products.updateOne(
  { name: "Cloud VPS M" },
  { $inc: { price: -50, viewCount: 1 } }
)

// $mul — คูณค่า
db.products.updateMany(
  { active: true },
  { $mul: { price: 1.1 } }  // ขึ้นราคา 10%
)

// $min, $max — อัพเดตถ้าค่าใหม่น้อยกว่า/มากกว่าค่าเดิม
db.products.updateOne(
  { name: "Cloud VPS M" },
  { $min: { price: 500 } }  // ลดเป็น 500 เฉพาะถ้าราคาเดิมมากกว่า
)

// $rename — เปลี่ยนชื่อ Field
db.products.updateMany(
  {},
  { $rename: { "cost": "price" } }
)

// $currentDate — กำหนดค่าเป็นวันที่ปัจจุบัน
db.products.updateOne(
  { name: "Cloud VPS M" },
  { $currentDate: { lastModified: true } }
)

Array Update Operators

// $push — เพิ่มสมาชิกเข้า Array
db.servers.updateOne(
  { name: "Web Server" },
  { $push: { tags: "monitored" } }
)

// $push + $each + $sort + $slice — เพิ่มหลายค่าพร้อมจัดเรียงและจำกัดจำนวน
db.servers.updateOne(
  { name: "Web Server" },
  {
    $push: {
      logs: {
        $each: [
          { ts: new Date(), msg: "CPU spike" },
          { ts: new Date(), msg: "Memory warning" }
        ],
        $sort: { ts: -1 },
        $slice: 100  // เก็บแค่ 100 รายการล่าสุด
      }
    }
  }
)

// $addToSet — เพิ่มเข้า Array เฉพาะถ้ายังไม่มี
db.servers.updateOne(
  { name: "Web Server" },
  { $addToSet: { tags: "production" } }
)

// $pull — ลบสมาชิกที่ตรงเงื่อนไขออกจาก Array
db.servers.updateOne(
  { name: "Web Server" },
  { $pull: { tags: "deprecated" } }
)

// $pop — ลบสมาชิกตัวแรก (-1) หรือตัวสุดท้าย (1)
db.servers.updateOne(
  { name: "Web Server" },
  { $pop: { logs: 1 } }  // ลบ log ตัวสุดท้าย
)

// $ (Positional Operator) — อัพเดต Element ที่ตรงเงื่อนไข
db.servers.updateOne(
  { "metrics.type": "cpu" },
  { $set: { "metrics.$.threshold": 90 } }
)

replaceOne — แทนที่ Document ทั้งตัว

// แทนที่ Document ทั้งหมด (ยกเว้น _id)
db.products.replaceOne(
  { name: "Cloud VPS M" },
  {
    name: "Cloud VPS M",
    cpu: 2,
    ram: 4,
    storage: 100,
    price: 590,
    active: true,
    features: ["SSD NVMe", "Root Access", "DDoS Protection"],
    updatedAt: new Date()
  }
)

Upsert — อัพเดตหรือสร้างใหม่

Upsert เป็นรูปแบบการอัพเดตที่มีประโยชน์มากในการทำงานจริง คือถ้าพบ Document ที่ตรงเงื่อนไขจะอัพเดต แต่ถ้าไม่พบจะสร้าง Document ใหม่โดยรวม Filter กับ Update เข้าด้วยกัน

// Upsert — ถ้ามีให้อัพเดต ถ้าไม่มีให้สร้างใหม่
db.configs.updateOne(
  { key: "maintenance_mode" },
  {
    $set: { value: false, updatedAt: new Date() },
    $setOnInsert: { createdAt: new Date() }
  },
  { upsert: true }
)

// $setOnInsert — กำหนดค่าเฉพาะตอนสร้างใหม่ ไม่แก้ตอนอัพเดต

findOneAndUpdate — อัพเดตแล้วคืน Document

// คืน Document ก่อนอัพเดต (Default)
db.counters.findOneAndUpdate(
  { name: "order_seq" },
  { $inc: { value: 1 } }
)

// คืน Document หลังอัพเดต
db.counters.findOneAndUpdate(
  { name: "order_seq" },
  { $inc: { value: 1 } },
  { returnDocument: "after" }
)

// มีประโยชน์สำหรับสร้าง Auto-increment Sequence

Delete — ลบ Document

การลบข้อมูลมี 2 คำสั่งหลัก คือ deleteOne สำหรับลบ Document แรกที่ตรงเงื่อนไข และ deleteMany สำหรับลบทุก Document ที่ตรงเงื่อนไข ควรระมัดระวังเรื่องเงื่อนไข Filter ให้ดี เพราะ MongoDB ไม่มี Trash หรือ Soft Delete ในตัว

// deleteOne — ลบ Document แรกที่ตรงเงื่อนไข
db.products.deleteOne({ name: "Cloud VPS XL" })

// deleteMany — ลบทุก Document ที่ตรงเงื่อนไข
db.products.deleteMany({ active: false })

// ลบทุก Document ใน Collection (ระวัง!)
db.logs.deleteMany({})

// findOneAndDelete — ลบแล้วคืน Document ที่ถูกลบ
db.tasks.findOneAndDelete(
  { status: "completed" },
  { sort: { completedAt: 1 } }  // ลบตัวที่เก่าที่สุด
)

Bulk Write — ทำหลายปฏิบัติการพร้อมกัน

เมื่อต้องทำหลายปฏิบัติการในครั้งเดียว Bulk Write ช่วยลดจำนวน Round-trip ไปยังเซิร์ฟเวอร์ฐานข้อมูล ทำให้ประสิทธิภาพดีขึ้นอย่างมากเมื่อเทียบกับการเรียกคำสั่งทีละตัว

db.products.bulkWrite([
  {
    insertOne: {
      document: { name: "Cloud VPS 2XL", cpu: 16, ram: 32, price: 3990 }
    }
  },
  {
    updateOne: {
      filter: { name: "Cloud VPS S" },
      update: { $set: { price: 250 } }
    }
  },
  {
    updateMany: {
      filter: { active: false },
      update: { $set: { archived: true } }
    }
  },
  {
    deleteOne: {
      filter: { name: "Deprecated Plan" }
    }
  },
  {
    replaceOne: {
      filter: { name: "Cloud VPS M" },
      replacement: { name: "Cloud VPS M", cpu: 2, ram: 4, price: 490 }
    }
  }
], { ordered: true })

// ordered: true (Default) — หยุดทันทีเมื่อ Error
// ordered: false — ข้าม Error แล้วทำต่อ

// ผลลัพธ์
// {
//   acknowledged: true,
//   insertedCount: 1,
//   matchedCount: 3,
//   modifiedCount: 3,
//   deletedCount: 1,
//   upsertedCount: 0
// }

ตัวอย่างการใช้งานจริงกับ Node.js

ในการพัฒนาเว็บแอปพลิเคชัน การเรียกใช้ CRUD ผ่าน Driver จะแตกต่างจาก mongosh เล็กน้อย ตัวอย่างนี้แสดงวิธีใช้งานกับ Node.js ซึ่งเป็นภาษาที่ใช้คู่กับ Document Database บ่อยที่สุด

const { MongoClient } = require('mongodb');

const uri = "mongodb://appUser:password@localhost:27017/myapp";
const client = new MongoClient(uri);

async function main() {
  await client.connect();
  const db = client.db("myapp");
  const products = db.collection("products");

  // Create
  const newProduct = await products.insertOne({
    name: "Cloud VPS M",
    price: 590,
    active: true
  });
  console.log("Inserted:", newProduct.insertedId);

  // Read
  const found = await products.find({ active: true })
    .sort({ price: 1 })
    .limit(10)
    .toArray();
  console.log("Found:", found.length, "products");

  // Update
  const updated = await products.updateOne(
    { name: "Cloud VPS M" },
    { $set: { price: 550 } }
  );
  console.log("Modified:", updated.modifiedCount);

  // Delete
  const deleted = await products.deleteMany({ active: false });
  console.log("Deleted:", deleted.deletedCount);

  await client.close();
}

main().catch(console.error);

ตัวอย่างการใช้งานกับ Python

Python เป็นอีกภาษาที่นิยมใช้กับฐานข้อมูลแบบ Document โดยเฉพาะงาน Data Processing และ Backend API ใช้ Driver ชื่อ PyMongo ซึ่งมี Syntax คล้ายกับ mongosh

from pymongo import MongoClient

client = MongoClient("mongodb://appUser:password@localhost:27017/myapp")
db = client["myapp"]
products = db["products"]

# Create
result = products.insert_one({
    "name": "Cloud VPS M",
    "price": 590,
    "active": True
})
print(f"Inserted: {result.inserted_id}")

# Read
for product in products.find({"active": True}).sort("price", 1).limit(10):
    print(product["name"], product["price"])

# Update
result = products.update_one(
    {"name": "Cloud VPS M"},
    {"$set": {"price": 550}}
)
print(f"Modified: {result.modified_count}")

# Delete
result = products.delete_many({"active": False})
print(f"Deleted: {result.deleted_count}")

client.close()

Write Concern — ควบคุมระดับความปลอดภัยในการเขียน

Write Concern กำหนดว่าฐานข้อมูลต้องยืนยันการเขียนในระดับไหนก่อนจะตอบกลับว่าสำเร็จ สำคัญมากในระบบ Replica Set เพราะส่งผลต่อความทนทานของข้อมูลและ Latency

// w: 1 — ยืนยันจาก Primary เท่านั้น (Default)
db.products.insertOne(
  { name: "Test" },
  { writeConcern: { w: 1 } }
)

// w: "majority" — ยืนยันจากเสียงส่วนใหญ่ใน Replica Set
db.products.insertOne(
  { name: "Critical Data" },
  { writeConcern: { w: "majority" } }
)

// w: 0 — ไม่รอยืนยัน (เร็วที่สุดแต่เสี่ยงสูญเสียข้อมูล)
db.logs.insertOne(
  { msg: "Non-critical log" },
  { writeConcern: { w: 0 } }
)

// j: true — ยืนยันว่าเขียนลง Journal แล้ว (ปลอดภัยที่สุด)
db.orders.insertOne(
  { orderId: "ORD-001", total: 1500 },
  { writeConcern: { w: "majority", j: true } }
)

Read Concern และ Read Preference

นอกจาก Write Concern แล้ว ยังมี Read Concern และ Read Preference ที่ควบคุมพฤติกรรมการอ่านข้อมูลในระบบ Replica Set

// Read Concern — ระดับความสอดคล้องของข้อมูลที่อ่าน
// "local" (Default) — อ่านข้อมูลล่าสุดจาก Node ที่เชื่อมต่อ
// "majority" — อ่านเฉพาะข้อมูลที่ยืนยันจากเสียงส่วนใหญ่แล้ว
// "linearizable" — อ่านข้อมูลล่าสุดที่ยืนยันแล้วจริง ๆ (ช้าที่สุด)

db.products.find({ active: true }).readConcern("majority")

// Read Preference — เลือก Node ที่จะอ่าน
// "primary" (Default) — อ่านจาก Primary เท่านั้น
// "primaryPreferred" — อ่านจาก Primary ถ้าพร้อม ไม่งั้นอ่านจาก Secondary
// "secondary" — อ่านจาก Secondary เท่านั้น
// "secondaryPreferred" — อ่านจาก Secondary ถ้าพร้อม ไม่งั้นอ่านจาก Primary
// "nearest" — อ่านจาก Node ที่ Latency ต่ำที่สุด

// เหมาะสำหรับ Report หรือ Analytics ที่ไม่ต้องการข้อมูลล่าสุดมาก
db.products.find().readPref("secondaryPreferred")

แนวทางปฏิบัติที่ดีสำหรับ CRUD

การใช้ CRUD อย่างมีประสิทธิภาพไม่ได้ขึ้นอยู่กับแค่คำสั่ง แต่ยังรวมถึงวิธีคิดและออกแบบ Query ที่เหมาะสมกับรูปแบบการใช้งาน

  • สร้าง Index บน Field ที่ใช้ในเงื่อนไข Filter บ่อย ๆ เพื่อเพิ่มความเร็วในการค้นหา
  • ใช้ Projection เพื่อดึงเฉพาะ Field ที่ต้องการ ลดปริมาณข้อมูลที่ส่งผ่าน Network
  • หลีกเลี่ยง skip จำนวนมากในการทำ Pagination ควรใช้ Range-based Pagination แทน เช่น ใช้ _id หรือ Timestamp เป็นเงื่อนไข
  • ใช้ bulkWrite เมื่อต้องทำหลายปฏิบัติการ เพื่อลด Network Round-trip
  • ตั้ง Write Concern เป็น majority สำหรับข้อมูลสำคัญ และใช้ w: 0 เฉพาะ Log ที่ยอมสูญเสียได้
  • ใช้ Upsert เมื่อต้องการ Insert-or-Update เพื่อลดจำนวนคำสั่ง
  • ทดสอบ Query ด้วย explain() ก่อนนำไปใช้งานจริง เพื่อตรวจสอบว่าใช้ Index หรือไม่
  • ระวังการใช้ updateMany กับเงื่อนไขที่กว้างเกินไป ควรทดสอบด้วย find ก่อนเสมอ

สรุป

CRUD Operations เป็นพื้นฐานสำคัญที่สุดในการทำงานกับ MongoDB ตั้งแต่การเพิ่มข้อมูลด้วย insertOne/insertMany การค้นหาด้วย find พร้อม Query Operators ที่หลากหลาย การแก้ไขด้วย Update Operators ที่ยืดหยุ่น ไปจนถึงการลบข้อมูลและการทำ Bulk Write สำหรับ Batch Operations การเข้าใจ Write Concern และ Read Concern ยังช่วยให้ควบคุมความปลอดภัยและประสิทธิภาพของข้อมูลได้อย่างเหมาะสม

แนะนำบริการ DE

การรัน MongoDB สำหรับงาน Production ต้องการเซิร์ฟเวอร์ที่มี RAM เพียงพอสำหรับ WiredTiger Cache และ SSD สำหรับ I/O ที่เร็ว Cloud VPS ของ DE รองรับการเลือก RAM และ SSD NVMe ตามความต้องการ พร้อม Root Access เต็มที่สำหรับติดตั้งและตั้งค่าฐานข้อมูลได้อย่างอิสระ

สำหรับโปรเจกต์ที่ใช้ Document Database ร่วมกับเว็บแอปพลิเคชันและไม่ต้องการจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE เป็นทางเลือกที่สะดวกพร้อม Managed Infrastructure ให้พร้อมใช้งาน