MongoDB Collections และ Documents

MongoDB เก็บข้อมูลในรูปแบบ Document ซึ่งแตกต่างจากฐานข้อมูล Relational ที่ใช้ตาราง (Table) และแถว (Row) ใน MongoDB หน่วยเก็บข้อมูลพื้นฐานคือ Document ที่อยู่ภายใน Collection โครงสร้างนี้ให้ความยืดหยุ่นสูง เพราะแต่ละ Document ไม่จำเป็นต้องมีโครงสร้างเดียวกัน เหมาะกับข้อมูลที่มี Schema เปลี่ยนแปลงบ่อยหรือมีลำดับชั้นซ้อนกัน

บทความนี้จะอธิบายแนวคิดของ Collection และ Document ใน MongoDB ตั้งแต่โครงสร้างพื้นฐาน การสร้างและจัดการ Collection ไปจนถึงรูปแบบข้อมูลของ Document รวมถึง Data Types ที่รองรับและแนวทางการออกแบบ Schema ที่ดี

Document คืออะไร

Document คือหน่วยข้อมูลพื้นฐานของ MongoDB เทียบได้กับแถว (Row) ในฐานข้อมูลแบบ Relational แต่เก็บข้อมูลในรูปแบบ BSON (Binary JSON) ซึ่งเป็น JSON ที่ถูกเข้ารหัสให้มีประสิทธิภาพสูงขึ้น ทุก Document ต้องมี Field ชื่อ _id เป็น Primary Key ที่ไม่ซ้ำกัน ถ้าไม่กำหนด MongoDB จะสร้าง ObjectId ให้อัตโนมัติ

// ตัวอย่าง Document
{
  "_id": ObjectId("665fa1b2c3d4e5f6a7b8c9d0"),
  "name": "สมชาย",
  "email": "[email protected]",
  "age": 28,
  "address": {
    "street": "ถนนสุขุมวิท",
    "city": "กรุงเทพ",
    "zipcode": "10110"
  },
  "hobbies": ["อ่านหนังสือ", "วิ่ง", "เขียนโค้ด"],
  "created_at": ISODate("2026-04-07T00:00:00Z")
}

จะเห็นว่า Document สามารถเก็บข้อมูลแบบซ้อน (Nested/Embedded) ได้ เช่น address เป็น Object ซ้อนอยู่ภายใน และ hobbies เป็น Array ทำให้สามารถเก็บข้อมูลที่สัมพันธ์กันไว้ในที่เดียวโดยไม่ต้อง JOIN ข้ามตารางเหมือนฐานข้อมูลแบบ Relational

Collection คืออะไร

Collection คือกลุ่มของ Documents เทียบได้กับตาราง (Table) ในฐานข้อมูลแบบ Relational แต่มีความแตกต่างสำคัญคือ Collection ไม่บังคับ Schema ดังนั้น Documents ภายใน Collection เดียวกันสามารถมีโครงสร้างที่แตกต่างกันได้ แม้ว่าในทางปฏิบัติมักจะออกแบบให้มีโครงสร้างคล้ายกันเพื่อความเป็นระเบียบ

// ใน Collection "users" สามารถมี Documents ที่โครงสร้างต่างกัน
// Document 1 — มี address
{
  "_id": ObjectId("..."),
  "name": "สมชาย",
  "email": "[email protected]",
  "address": { "city": "กรุงเทพ" }
}

// Document 2 — ไม่มี address แต่มี phone
{
  "_id": ObjectId("..."),
  "name": "สมหญิง",
  "email": "[email protected]",
  "phone": "081-xxx-xxxx"
}

การสร้างและจัดการ Collection

MongoDB สร้าง Collection อัตโนมัติเมื่อใส่ข้อมูลครั้งแรก (Implicit Creation) แต่ก็สามารถสร้างอย่างชัดเจนพร้อมกำหนดตัวเลือกเพิ่มเติมได้ (Explicit Creation)

// Implicit — สร้างอัตโนมัติเมื่อ Insert ข้อมูลแรก
use mydb
db.products.insertOne({ name: "Laptop", price: 25000 })
// Collection "products" ถูกสร้างโดยอัตโนมัติ

// Explicit — สร้างพร้อมกำหนดตัวเลือก
db.createCollection("logs", {
  capped: true,           // จำกัดขนาด Collection
  size: 10485760,         // 10 MB
  max: 10000              // เก็บได้สูงสุด 10,000 Documents
})

// ดู Collections ทั้งหมดในฐานข้อมูลปัจจุบัน
show collections

// ลบ Collection
db.logs.drop()

// เปลี่ยนชื่อ Collection
db.products.renameCollection("items")

Capped Collection

Capped Collection เป็น Collection แบบพิเศษที่มีขนาดคงที่ เมื่อข้อมูลเต็มจะเขียนทับข้อมูลเก่าที่สุดโดยอัตโนมัติ (FIFO) เหมาะสำหรับเก็บ Log, Cache หรือข้อมูลที่ต้องการเก็บแค่ช่วงเวลาล่าสุด ข้อจำกัดคือไม่สามารถลบ Document เดี่ยวได้ ไม่รองรับ Sharding และขนาดของ Document ที่ Update ต้องไม่ใหญ่กว่าเดิม

// สร้าง Capped Collection สำหรับ Application Log
db.createCollection("app_logs", {
  capped: true,
  size: 52428800,    // 50 MB
  max: 50000         // เก็บได้สูงสุด 50,000 Documents
})

// ตรวจสอบว่าเป็น Capped Collection หรือไม่
db.app_logs.isCapped()
// true

// แปลง Collection ปกติเป็น Capped
db.runCommand({
  convertToCapped: "old_logs",
  size: 10485760
})

Data Types ที่รองรับ

BSON รองรับ Data Types หลากหลายกว่า JSON มาตรฐาน การเลือกใช้ Type ที่ถูกต้องมีผลต่อประสิทธิภาพการ Query และพื้นที่จัดเก็บ

// ตัวอย่าง Data Types ทั้งหมดใน BSON
{
  // String — ข้อความ
  "name": "MongoDB Guide",

  // Number — ตัวเลข (Integer, Long, Double, Decimal128)
  "views": 1500,                    // Int32
  "views_total": NumberLong(999999999),  // Int64
  "price": 29.99,                   // Double
  "precise_amount": NumberDecimal("19.99"),  // Decimal128

  // Boolean
  "is_active": true,

  // Date
  "created_at": ISODate("2026-04-07T10:30:00Z"),
  "timestamp": Timestamp(1712500000, 1),

  // ObjectId — 12-byte unique identifier
  "ref_id": ObjectId("665fa1b2c3d4e5f6a7b8c9d0"),

  // Array — ลิสต์ข้อมูล
  "tags": ["database", "nosql", "tutorial"],

  // Embedded Document — Object ซ้อน
  "author": {
    "name": "Admin",
    "role": "editor"
  },

  // Binary Data
  "file_data": BinData(0, "base64encodeddata"),

  // Null
  "deleted_at": null,

  // Regular Expression
  "pattern": /^mongodb/i
}

สำหรับข้อมูลตัวเลขที่ต้องการความแม่นยำสูง เช่น จำนวนเงิน ควรใช้ Decimal128 แทน Double เพราะ Double มีปัญหาเรื่อง Floating Point Precision สำหรับค่าวันเวลา ควรใช้ ISODate แทน String เพื่อให้สามารถ Query ช่วงเวลาและเรียงลำดับได้ถูกต้อง

ObjectId อธิบายเชิงลึก

ObjectId เป็น Data Type ขนาด 12 bytes ที่ MongoDB ใช้เป็นค่า Default สำหรับ _id Field โครงสร้างประกอบด้วย 4 bytes แรกเป็น Unix Timestamp (วินาที), 5 bytes ถัดมาเป็น Random Value ที่สร้างครั้งเดียวต่อ Process และ 3 bytes สุดท้ายเป็น Counter ที่เพิ่มขึ้นเรื่อย ๆ

// สร้าง ObjectId ใหม่
let id = new ObjectId()

// ดึง Timestamp จาก ObjectId
id.getTimestamp()
// ISODate("2026-04-07T00:45:00Z")

// ใช้ ObjectId ในการ Query ตามช่วงเวลา
// หา Documents ที่สร้างหลังวันที่ 1 เม.ย. 2026
db.users.find({
  _id: { $gt: ObjectId("660b0a0000000000000000000") }
})

// สร้าง ObjectId จาก Hex String
let customId = ObjectId("665fa1b2c3d4e5f6a7b8c9d0")

เนื่องจาก ObjectId มี Timestamp อยู่ภายใน จึงสามารถเรียงลำดับตาม _id เพื่อเรียงตามเวลาที่สร้างได้โดยประมาณ โดยไม่ต้องสร้าง Index เพิ่มสำหรับ Field วันที่

Embedded Documents vs References

การออกแบบ Schema ใน MongoDB ต้องเลือกระหว่าง 2 แนวทางหลัก คือ Embedding (ฝังข้อมูลภายใน Document เดียวกัน) และ Referencing (อ้างอิงไปยัง Document อื่นด้วย _id) แต่ละแบบมีข้อดีและข้อจำกัดแตกต่างกัน

Embedding (Denormalization)

// แบบ Embedded — ข้อมูล order ฝังรายการสินค้าไว้ภายใน
{
  "_id": ObjectId("..."),
  "order_number": "ORD-2026-001",
  "customer": {
    "name": "สมชาย",
    "email": "[email protected]"
  },
  "items": [
    { "product": "Laptop", "qty": 1, "price": 25000 },
    { "product": "Mouse", "qty": 2, "price": 350 }
  ],
  "total": 25700,
  "status": "shipped"
}

Embedding เหมาะเมื่อข้อมูลที่สัมพันธ์กันมักถูกเรียกใช้พร้อมกัน (เช่น คำสั่งซื้อกับรายการสินค้า), ข้อมูลที่ฝังไม่โตไม่จำกัด และเมื่อต้องการ Read Performance สูงเพราะไม่ต้อง Query หลาย Collection

Referencing (Normalization)

// แบบ Reference — แยก Collection แล้วอ้างอิงด้วย _id
// Collection: orders
{
  "_id": ObjectId("..."),
  "order_number": "ORD-2026-001",
  "customer_id": ObjectId("665fa1b2c3d4e5f6a7b8c9d0"),
  "item_ids": [
    ObjectId("...item1..."),
    ObjectId("...item2...")
  ],
  "total": 25700,
  "status": "shipped"
}

// Collection: customers
{
  "_id": ObjectId("665fa1b2c3d4e5f6a7b8c9d0"),
  "name": "สมชาย",
  "email": "[email protected]"
}

// ดึงข้อมูลด้วย $lookup (เหมือน JOIN)
db.orders.aggregate([
  {
    $lookup: {
      from: "customers",
      localField: "customer_id",
      foreignField: "_id",
      as: "customer"
    }
  }
])

Referencing เหมาะเมื่อข้อมูลที่สัมพันธ์กันมีขนาดใหญ่หรือโตไม่จำกัด (เช่น Comments ของ Post), เมื่อต้องการ Update ข้อมูลที่ใช้ร่วมกันในที่เดียว (เช่น ข้อมูลลูกค้าที่ปรากฏในหลายคำสั่งซื้อ) หรือเมื่อต้องการลด Duplication

Schema Validation

แม้ว่า MongoDB จะเป็น Schema-less แต่ในระบบ Production ควรใช้ Schema Validation เพื่อบังคับโครงสร้างข้อมูลขั้นต่ำ ป้องกัน Document ที่ไม่ถูกต้องถูก Insert เข้ามา

// สร้าง Collection พร้อม Schema Validation
db.createCollection("products", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name", "price", "category"],
      properties: {
        name: {
          bsonType: "string",
          description: "ชื่อสินค้า — ต้องเป็น String"
        },
        price: {
          bsonType: "number",
          minimum: 0,
          description: "ราคา — ต้องเป็นตัวเลขไม่ติดลบ"
        },
        category: {
          enum: ["electronics", "clothing", "food", "other"],
          description: "หมวดหมู่ — ต้องเป็นค่าที่กำหนด"
        },
        tags: {
          bsonType: "array",
          items: { bsonType: "string" }
        }
      }
    }
  },
  validationLevel: "strict",     // strict หรือ moderate
  validationAction: "error"      // error หรือ warn
})

// เพิ่ม Validation ให้ Collection ที่มีอยู่แล้ว
db.runCommand({
  collMod: "products",
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name", "price"]
    }
  }
})

// ทดสอบ — Insert ที่ไม่ผ่าน Validation จะ Error
db.products.insertOne({ name: "Laptop" })
// Error: Document failed validation (ขาด price และ category)

ค่า validationLevel มี 2 แบบ คือ strict ที่ตรวจสอบทุก Insert และ Update กับ moderate ที่ตรวจสอบเฉพาะ Document ที่ตรงกับ Schema เดิมอยู่แล้ว ส่วน validationAction มี error ที่ปฏิเสธการเขียน กับ warn ที่ยอมให้เขียนแต่บันทึก Warning ลง Log

แนวทางการออกแบบ Schema

การออกแบบ Schema ที่ดีขึ้นอยู่กับรูปแบบการใช้งาน (Access Pattern) ไม่ใช่แค่โครงสร้างข้อมูล หลักการสำคัญมีดังนี้

ข้อมูลที่ถูกเรียกใช้พร้อมกันควรเก็บไว้ด้วยกัน (Data that is accessed together should be stored together) เช่น ถ้าหน้าแสดงสินค้าต้องแสดงรีวิวด้วย ควร Embed รีวิวไว้ใน Document สินค้า แต่ถ้ารีวิวมีจำนวนมากและอาจโตไม่จำกัด ควรใช้ Reference แทน

ระวังขนาดของ Document เพราะ BSON มีขีดจำกัดที่ 16 MB ต่อ Document ถ้าออกแบบ Schema ที่ให้ Array โตไม่จำกัด อาจชนขีดจำกัดนี้ได้ ควรจำกัดจำนวนสมาชิกใน Array หรือแยกเป็น Collection ต่างหากเมื่อข้อมูลมีแนวโน้มโตมาก

หลีกเลี่ยง Deeply Nested Documents เพราะ Nesting ลึกเกินไป (เกิน 3-4 ระดับ) ทำให้ Query ซับซ้อน Update ยาก และ Index ไม่มีประสิทธิภาพ ถ้าโครงสร้างซ้อนลึกมาก ควรพิจารณาแยกเป็น Collection

ตัวอย่างรูปแบบ Schema ที่พบบ่อย

// 1. One-to-One — Embed ภายใน Document เดียวกัน
// เช่น User กับ Profile
{
  "_id": ObjectId("..."),
  "username": "somchai",
  "profile": {
    "full_name": "สมชาย ใจดี",
    "bio": "Developer",
    "avatar_url": "/avatars/somchai.jpg"
  }
}

// 2. One-to-Few — Embed เป็น Array
// เช่น User กับ Address (คนหนึ่งมีไม่กี่ที่อยู่)
{
  "_id": ObjectId("..."),
  "username": "somchai",
  "addresses": [
    { "label": "บ้าน", "city": "กรุงเทพ" },
    { "label": "ที่ทำงาน", "city": "นนทบุรี" }
  ]
}

// 3. One-to-Many — Reference ด้วย Array ของ ObjectId
// เช่น Author กับ Books
// Collection: authors
{ "_id": ObjectId("...a1..."), "name": "นักเขียน ก" }

// Collection: books
{ "_id": ObjectId("...b1..."), "title": "หนังสือเล่ม 1", "author_id": ObjectId("...a1...") }
{ "_id": ObjectId("...b2..."), "title": "หนังสือเล่ม 2", "author_id": ObjectId("...a1...") }

// 4. Many-to-Many — Array ของ Reference ทั้ง 2 ฝั่ง
// Collection: students
{
  "_id": ObjectId("...s1..."),
  "name": "นักเรียน ก",
  "course_ids": [ObjectId("...c1..."), ObjectId("...c2...")]
}

// Collection: courses
{
  "_id": ObjectId("...c1..."),
  "name": "Database 101",
  "student_ids": [ObjectId("...s1..."), ObjectId("...s2...")]
}

สรุป

Collection และ Document เป็นหัวใจหลักของการจัดเก็บข้อมูลใน MongoDB การเข้าใจโครงสร้าง Data Types และความแตกต่างระหว่าง Embedding กับ Referencing จะช่วยให้ออกแบบ Schema ได้เหมาะสมกับรูปแบบการใช้งานจริง สิ่งสำคัญคือออกแบบตาม Access Pattern ใช้ Schema Validation ป้องกันข้อมูลผิดรูปแบบ และระวังขนาดของ Document ไม่ให้เกินขีดจำกัด 16 MB

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

การทดลองใช้งาน MongoDB กับข้อมูลจริงต้องการเซิร์ฟเวอร์ที่มี RAM เพียงพอสำหรับ Working Set และ SSD สำหรับ I/O ที่รวดเร็ว Cloud VPS ของ DE รองรับการเลือกสเปค RAM และ NVMe SSD ตามต้องการ พร้อม Root Access เต็มที่สำหรับติดตั้งและทดลอง Schema Design ได้อย่างอิสระ

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