Database SQL PostgreSQL

Database Design & SQL — ออกแบบฐานข้อมูลที่ดีและเขียน SQL อย่างมืออาชีพ 🗄️

By Anirach Mingkhwan DevOps & Vibe Coding 2026
Database Design & SQL

ทุก App ที่คุณใช้ทุกวันมี Database อยู่ข้างหลัง — LINE (messages, contacts), Shopee (products, orders), YouTube (videos, comments), Banking App (accounts, transactions) 🏦

💡 "App without a database is just a pretty picture" — Code เปลี่ยนได้ทุกวัน, Framework เปลี่ยนได้ทุกปี แต่ Database schema ที่ออกแบบผิด → ปวดหัวไปตลอดชีวิต

📊 Database Types — เลือกอะไรดี?

ก่อนที่เราจะเจาะลึกเรื่อง SQL เราต้องเข้าใจก่อนว่ามี Database หลายประเภท แต่ละแบบมีจุดเด่นและเหมาะกับงานแบบไหน การเลือก Database ที่ถูกต้องตั้งแต่เริ่มต้นจะช่วยให้โปรเจคของคุณเติบโตได้อย่างมีประสิทธิภาพ

ในโลกของ Database มี 2 กลุ่มใหญ่ที่เราต้องเลือก: Relational Database (SQL) ที่ข้อมูลมีโครงสร้างและความสัมพันธ์ที่ชัดเจน กับ NoSQL Database ที่ยืดหยุ่นในเรื่องโครงสร้างมากกว่า แต่ละแบบมีเหตุผลในการใช้งานที่แตกต่างกัน

Relational (SQL)
MySQL / MariaDB
SQLite
SQL Server
  • ข้อมูลมีความสัมพันธ์กัน
  • ต้องการ ACID
  • Complex queries
  • Transaction สำคัญ
  • 90% ของ apps
NoSQL
MongoDB (Document)
Redis (Key-Value)
Cassandra (Wide)
Neo4j (Graph)
  • Schema ไม่แน่นอน
  • ต้องการ scale แนวนอน
  • High throughput
  • Real-time / Cache
  • Specific use cases

จากการเปรียบเทียบข้างต้น คุณจะเห็นว่า Relational Database ยังคงเป็นตัวเลือกหลักสำหรับแอปพลิเคชันส่วนใหญ่ เพราะข้อมูลในแอปทั่วไปมักมีความสัมพันธ์กัน เช่น ลูกค้า-คำสั่งซื้อ, โพสต์-ความคิดเห็น, ผู้ใช้-เพื่อน

ในกลุ่ม Relational Database เอง PostgreSQL เป็นตัวเลือกที่แนะนำที่สุดในปัจจุบัน ด้วยความสามารถที่หลากหลายและฟีเจอร์ที่ทันสมัย แต่หลายๆ องค์กรยังใช้ MySQL เป็นหลัก มาดูเปรียบเทียบกันว่าต่างกันอย่างไร

PostgreSQL vs MySQL — เปรียบเทียบ

PostgreSQL ⭐MySQL
LicenseBSD (เสรีจริงๆ)GPL (Oracle)
ACID✅ Full✅ (InnoDB)
JSON support⭐ JSONB (เร็ว)JSON (ช้ากว่า)
Full-text search✅ Built-in✅ Basic
ExtensionsPostGIS, pg_trgm, pgvector, timescaleLimited
Complex queries⭐ CTE, Window functions, lateral✅ OK
PerformanceComplex queries ดีกว่าSimple reads ดีกว่า
ใช้โดยInstagram, Stripe, Discord, RedditFacebook, Uber, Airbnb, Twitter

จากตารางเปรียบเทียบข้างต้น คุณจะเห็นว่า PostgreSQL มีความได้เปรียบในหลายๆ ด้าน โดยเฉพาะการรองรับ JSONB ที่ทำให้สามารถเก็บข้อมูลแบบ NoSQL ได้ภายใน Relational Database เดียวกัน รวมถึง Extensions ที่ทรงพลังเช่น PostGIS สำหรับข้อมูลเชิงพื้นที่

MySQL ก็ยังมีจุดเด่นในเรื่องความเร็วของ Simple Reads และการมี Community ที่ใหญ่ แต่สำหรับโปรเจคใหม่ PostgreSQL มักจะเป็นทางเลือกที่ดีกว่าเพราะความยืดหยุ่นและฟีเจอร์ที่ครบครัน

💡 แนะนำ: เริ่มต้นที่ PostgreSQL — ทำได้ทุกอย่าง
SQLite สำหรับ prototype / mobile / embedded

🏗️ Database Design — ออกแบบยังไงให้ดี?

การออกแบบ Database ที่ดีคือรากฐานของแอปพลิเคชันที่มีประสิทธิภาพ การออกแบบที่ผิดพลาดจะทำให้เราต้องแก้ไขในภายหลังด้วยความยากลำบาก ซึ่งเป็นเรื่องที่ค่อนข้างเสี่ยงและใช้เวลามาก

หลักการสำคัญในการออกแบบคือการลด Data Redundancy (ข้อมูลซ้ำซ้อน) และรักษา Data Integrity (ความถูกต้องของข้อมูล) มาดูตัวอย่างระบบ E-Commerce แบบง่ายๆ กันว่าการออกแบบที่ถูกและผิดต่างกันอย่างไร

ตัวอย่าง: ระบบ E-Commerce

สมมติว่าเราต้องเก็บข้อมูลลูกค้า สินค้า และคำสั่งซื้อ หลายๆ คนเริ่มต้นด้วยการยัดข้อมูลทุกอย่างลงใน table เดียว ซึ่งดูง่ายแต่จะสร้างปัญหาใหญ่ในอนาคต

❌ แบบผิด — ยัดทุกอย่างใน table เดียว

orders table (🤮 ทุกอย่างอยู่ที่เดียว):

id | customer_name | customer_email | customer_phone | product_name | product_price | product_category | quantity | total | order_date | shipping_address | shipping_city | ...

ปัญหา:

  • ข้อมูลซ้ำ: customer_name ซ้ำทุก order
  • แก้ยาก: เปลี่ยนเบอร์โทร → ต้องแก้ทุก row
  • Inconsistent: บาง row ชื่อ "สมชาย" บาง row "Somchai"
  • ช้า: table ใหญ่ขึ้นเรื่อยๆ query ช้าลง
✅ แบบถูก — แยก tables + สร้างความสัมพันธ์
customers
id PK
name
email
phone
created_at
orders
id PK
customer_id FK
order_date
status
total
products
id PK
name
price
category_id
stock
order_items
order_id FK
product_id FK
quantity
unit_price

ตัวอย่างข้างต้นแสดงให้เห็นความแตกต่างอย่างชัดเจนระหว่างการออกแบบแบบผิดและถูก แบบผิดจะทำให้ข้อมูลซ้ำซ้อน แก้ไขยาก และเกิดความไม่สอดคล้องได้ง่าย ขณะที่แบบถูกจะแยกข้อมูลออกเป็นหลาย tables และใช้ Foreign Keys เชื่อมความสัมพันธ์

การแยก tables ด้วย Primary Keys และ Foreign Keys ทำให้:

หลักการนี้เรียกว่า Normalization ซึ่งเป็นกระบวนการลดความซ้ำซ้อนของข้อมูลอย่างเป็นระบบ

Normalization — ลดความซ้ำซ้อน

1NF (First Normal Form)
ทุก column มีค่าเดียว (atomic)
❌ phone: "081-xxx, 082-xxx"
✅ แยกเป็น phone_numbers table
2NF (Second Normal Form)
ผ่าน 1NF + ทุก column ขึ้นกับ PK ทั้งหมด (ไม่ใช่แค่บางส่วน)
❌ order_items(order_id, product_id, product_name)
product_name ขึ้นกับ product_id อย่างเดียว
✅ ย้าย product_name ไป products table
3NF (Third Normal Form)
ผ่าน 2NF + ไม่มี transitive dependency
❌ orders(customer_id, customer_name, customer_city)
customer_name ขึ้นกับ customer_id ไม่ใช่ order
✅ ย้ายข้อมูล customer ไป customers table

Normalization แต่ละระดับมีจุดประสงค์และข้อดีที่ชัดเจน 1NF ทำให้ข้อมูลมีโครงสร้างพื้นฐาน 2NF ลดการเก็บข้อมูลซ้ำที่เกิดจาก Composite Keys และ 3NF ตัดการพึ่งพาทางอ้อมออกไป

ในการทำงานจริง การออกแบบถึง 3NF มักจะเพียงพอแล้ว ถ้าไปไกลกว่านี้จะซับซ้อนเกินความจำเป็น ส่วน Denormalization (การรวม tables กลับเข้าด้วยกัน) ควรทำเฉพาะเมื่อมี Performance Issue จริงๆ และวัดผลได้ชัดเจน

💡 ในทางปฏิบัติ: ออกแบบถึง 3NF ก็เพียงพอ
Denormalize เฉพาะเมื่อ performance ต้องการจริงๆ

เมื่อออกแบบ Database เสร็จแล้ว ขั้นตอนต่อไปคือการเรียนรู้ SQL (Structured Query Language) ซึ่งเป็นภาษาสำหรับจัดการข้อมูลใน Relational Database ทุกระบบ SQL มีพื้นฐานคล้ายๆ กัน แต่แต่ละตัวก็มี Extensions และฟีเจอร์พิเศษของตัวเอง


📝 SQL Essentials — คำสั่งที่ต้องรู้

CREATE TABLE — สร้าง Table

การสร้าง Table เป็นขั้นตอนแรกของการทำงานกับ Database คำสั่ง CREATE TABLE ใน SQL ใช้ในการกำหนดโครงสร้างของ Table รวมถึง Data Types, Constraints, และความสัมพันธ์ต่างๆ

ตัวอย่างนี้จะแสดงการสร้าง Tables สำหรับระบบ E-Commerce ที่เรากล่าวถึงก่อนหน้านี้ โดยใช้ PostgreSQL Syntax โปรดสังเกต Constraints ต่างๆ เช่น PRIMARY KEY, FOREIGN KEY, CHECK, UNIQUE ที่ช่วยรักษาความถูกต้องของข้อมูล

-- ═══════════════════════════════
-- สร้าง Tables สำหรับ E-Commerce
-- ═══════════════════════════════

-- Categories
CREATE TABLE categories (
    id          SERIAL PRIMARY KEY,
    name        VARCHAR(100) NOT NULL UNIQUE,
    description TEXT,
    parent_id   INT REFERENCES categories(id),  -- self-reference
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Products
CREATE TABLE products (
    id          SERIAL PRIMARY KEY,
    name        VARCHAR(255) NOT NULL,
    slug        VARCHAR(255) NOT NULL UNIQUE,     -- URL-friendly name
    description TEXT,
    price       DECIMAL(10,2) NOT NULL CHECK (price >= 0),
    stock       INT NOT NULL DEFAULT 0 CHECK (stock >= 0),
    category_id INT REFERENCES categories(id),
    is_active   BOOLEAN DEFAULT true,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

จากตัวอย่างข้างต้น คุณจะเห็นการใช้ Constraints ต่างๆ ที่สำคัญ:

การเลือก Data Type ที่เหมาะสมสำคัญมาก เพราะส่งผลต่อ Storage Space, Performance และความถูกต้องของข้อมูล มาดู Data Types ที่ใช้บ่อยในแต่ละหมวดหมู่

Data Types ที่ใช้บ่อย

Text
VARCHAR(n) จำกัดความยาว
TEXT ไม่จำกัดความยาว
CHAR(n) ความยาวคงที่
Numbers
INT/INTEGER ตัวเลขจำนวนเต็ม
DECIMAL(p,s) ตัวเลขแม่นยำ (เงิน!)
SERIAL auto-increment
Date/Time
TIMESTAMP วันที่ + เวลา
TIMESTAMPTZ + timezone (แนะนำ!)
DATE วันที่อย่างเดียว
Other
BOOLEAN true/false
JSONB JSON (searchable)
UUID unique identifier

การเลือก Data Type ที่เหมาะสมจะช่วยประหยัด Storage และเพิ่ม Performance VARCHAR เหมาะกับข้อมูลที่มีความยาวไม่แน่นอน TEXT สำหรับข้อมูลยาวๆ เช่น บทความ INT สำหรับตัวเลขจำนวนเต็ม และ DECIMAL สำหรับการคำนวณที่ต้องการความแม่นยำ

โดยเฉพาะ TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) ใน PostgreSQL ซึ่งแนะนำมากเพราะจัดการ Time Zone ให้อัตโนมัติ ไม่ต้องไปปวดหัวกับปัญหาเวลาที่แตกต่างกันในแต่ละประเทศ

💡 เงิน → DECIMAL ไม่ใช่ FLOAT!
FLOAT: 0.1 + 0.2 = 0.30000000000000004
DECIMAL: 0.1 + 0.2 = 0.3 ✅

เมื่อสร้าง Tables เสร็จแล้ว ขั้นตอนต่อไปคือการทำงานกับข้อมูล หรือที่เราเรียกว่า CRUD Operations ซึ่งครอบคลุมการทำงานพื้นฐานทั้งหมดที่เราต้องทำกับ Database

CRUD — Create, Read, Update, Delete

-- ═══════════════════════════════
-- INSERT — เพิ่มข้อมูล
-- ═══════════════════════════════

-- เพิ่ม 1 record
INSERT INTO customers (name, email, phone, password_hash)
VALUES ('สมชาย ใจดี', '[email protected]', '081-234-5678', '$2b$10$hash...');

-- เพิ่มหลาย records
INSERT INTO products (name, slug, price, stock, category_id) VALUES
    ('iPhone 16 Pro', 'iphone-16-pro', 45900.00, 50, 1),
    ('MacBook Air M4', 'macbook-air-m4', 42900.00, 30, 2),
    ('AirPods Pro 3', 'airpods-pro-3', 8900.00, 100, 3);

-- INSERT + RETURNING (ได้ค่ากลับมา)
INSERT INTO orders (customer_id, status, total)
VALUES (1, 'pending', 54800.00)
RETURNING id, ordered_at;

คำสั่ง INSERT ข้างต้นแสดงวิธีการเพิ่มข้อมูลทั้งแบบ Single Record และ Multiple Records พร้อมกัน การใช้ INSERT ... RETURNING เป็นฟีเจอร์ที่มีประโยชน์มาก เพราะจะได้ค่า ID หรือค่าอื่นๆ ที่ Database สร้างให้กลับมาทันที

แต่การใส่ข้อมูลเพียงอย่างเดียวไม่เพียงพอ เราต้องสามารถ Query ข้อมูลจากหลาย Tables พร้อมกันด้วย นี่คือจุดที่ JOIN เข้ามามีบทบาทสำคัญ

JOIN — เชื่อมข้อมูลจากหลาย Tables

-- ═══════════════════════════════
-- INNER JOIN — เอาเฉพาะที่ match ทั้ง 2 ฝั่ง
-- ═══════════════════════════════

SELECT c.name, o.id AS order_id, o.total, o.status
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;

-- LEFT JOIN — เอาทุก row จากซ้าย + match จากขวา (ถ้ามี)
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;
-- → ได้ลูกค้าทุกคน แม้ยังไม่เคยสั่ง (order_count = 0)

JOIN เป็นหัวใจสำคัญของ Relational Database ที่ทำให้เราสามารถเชื่อมข้อมูลจากหลาย Tables มาแสดงผลร่วมกัน INNER JOIN จะได้เฉพาะข้อมูลที่ match ทั้งสองฝั่ง ส่วน LEFT JOIN จะได้ข้อมูลทั้งหมดจากฝั่งซ้าย และข้อมูลจากฝั่งขวาเฉพาะที่ match

การเข้าใจ JOIN Types ต่างๆ เป็นสิ่งสำคัญ เพราะจะส่งผลต่อผลลัพธ์ที่ได้ เช่น การนับจำนวน Orders ของลูกค้าแต่ละคน ถ้าใช้ INNER JOIN จะได้เฉพาะลูกค้าที่เคยสั่งซื้อ แต่ถ้าใช้ LEFT JOIN จะได้ลูกค้าทุกคน รวมถึงคนที่ยังไม่เคยสั่งซื้อ (แสดงเป็น 0)

แต่เมื่อข้อมูลมากขึ้น Query จะช้าลง ซึ่งนี่คือจุดที่ Indexing เข้ามาช่วยทำให้ Query เร็วขึ้นอย่างมหาศาล


⚡ Indexing — ทำให้ Query เร็วขึ้น 100x

Without Index
Database: "ต้อง scan ทุก row..."
📄📄📄📄📄📄📄📄📄📄 → 1,000,000 rows
⏱️ 2.3 seconds
With Index
Database: "ดู index → ไปตรง row ที่ต้องการ"
🔍 → 📄 → found!
⏱️ 0.002 seconds (1000x เร็วขึ้น!)

การเปรียบเทียบข้างต้นแสดงให้เห็นพลังของ Index อย่างชัดเจน การที่ Database ต้อง Scan ข้อมูล 1 ล้าน Records ใช้เวลา 2.3 วินาที แต่เมื่อมี Index แล้วใช้เวลาแค่ 0.002 วินาที นั่นคือเร็วขึ้น 1000 เท่า!

Index = สารบัญหนังสือ — ไม่มี index = ต้องอ่านหนังสือทั้งเล่มเพื่อหาหน้าที่ต้องการ

แต่ Index ไม่ใช่ยาวิเศษที่ใส่ได้ไม่จำกัด Index แต่ละตัวใช้ Storage และทำให้ INSERT/UPDATE ช้าลง เพราะต้องไปอัพเดต Index ด้วย ดังนั้นต้องสร้าง Index อย่างชาญฉลาดตามการใช้งานจริง

-- ═══════════════════════════════
-- สร้าง Index
-- ═══════════════════════════════

-- B-tree (default) — ใช้ได้กับ =, <, >, BETWEEN, ORDER BY
CREATE INDEX idx_products_price ON products(price);
CREATE INDEX idx_orders_status ON orders(status);

-- Unique index — ป้องกันค่าซ้ำ + เร็ว
CREATE UNIQUE INDEX idx_customers_email ON customers(email);

-- Composite index — หลาย columns
CREATE INDEX idx_orders_customer_status
ON orders(customer_id, status);

-- Partial index — index เฉพาะบาง rows
CREATE INDEX idx_active_products
ON products(name, price)
WHERE is_active = true;

ตัวอย่างการสร้าง Index ข้างต้นแสดงหลายแบบที่ใช้ในงานจริง:

หลังจากสร้าง Index แล้ว เราจำเป็นต้องตรวจสอบว่า Database ใช้ Index หรือไม่ ผ่านคำสั่ง EXPLAIN ANALYZE ที่เป็นเครื่องมือสำคัญสำหรับ Performance Tuning

EXPLAIN ANALYZE — วิเคราะห์ Performance

EXPLAIN ANALYZE
SELECT p.name, p.price
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.name = 'Electronics'
  AND p.price < 50000
  AND p.is_active = true
ORDER BY p.price DESC
LIMIT 10;

-- ผลลัพธ์:
-- Limit  (cost=12.45..12.48 rows=10) (actual time=0.089..0.091 rows=10 loops=1)
--   ->  Sort  (cost=12.45..12.58 rows=50) (actual time=0.088..0.089 rows=10)
--         Sort Key: p.price DESC
--         ->  Nested Loop  (cost=0.43..11.89 rows=50) (actual time=0.031..0.072)
--               ->  Index Scan using idx_categories_name on categories c  ← ✅ ใช้ index!
--               ->  Index Scan using idx_products_category on products p  ← ✅ ใช้ index!

ผลลัพธ์จาก EXPLAIN ANALYZE แสดงให้เห็น Query Plan ที่ Database เลือกใช้ การที่เห็น "Index Scan" แสดงว่า Database ใช้ Index เราได้สำเร็จ ส่วนถ้าเห็น "Seq Scan" (Sequential Scan) แสดงว่าไม่มี Index ที่เหมาะสม จำเป็นต้องไปสร้างเพิ่มเติม

ค่า "cost" และ "actual time" ที่แสดงออกมาช่วยให้เราเปรียบเทียบ Performance ได้ และค่า "rows" บอกจำนวน Records ที่ประมวลผล ข้อมูลเหล่านี้ช่วยให้เราตัดสินใจได้ว่าควรสร้าง Index เพิ่มหรือไม่

แต่การสร้าง Index เป็นเพียงส่วนหนึ่งของการจัดการ Database เราต้องมีวิธีจัดการการเปลี่ยนแปลง Schema อย่างเป็นระบบด้วย ซึ่งนั่นคือจุดที่ Database Migrations เข้ามามีบทบาท


🔄 Migrations — จัดการ Schema Changes

ทำไมต้อง Migrations? → ทุก schema change เป็น code file, version controlled (git), รันซ้ำได้, rollback ได้, ทุกคนใน team ได้ schema เดียวกัน

Migration ด้วย Knex.js (Node.js)

// migrations/20260307_001_create_products.js

exports.up = function(knex) {
  return knex.schema.createTable('products', (table) => {
    table.increments('id');
    table.string('name', 255).notNullable();
    table.string('slug', 255).notNullable().unique();
    table.text('description');
    table.decimal('price', 10, 2).notNullable();
    table.integer('stock').defaultTo(0);
    table.integer('category_id')
      .references('id').inTable('categories');
    table.boolean('is_active').defaultTo(true);
    table.timestamps(true, true);   // created_at, updated_at

    // Indexes
    table.index('category_id');
    table.index('price');
    table.index(['is_active', 'price']);  // composite
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable('products');
};

ตัวอย่าง Migration ข้างต้นแสดงโครงสร้างที่ดี มีทั้ง up() function สำหรับการเปลี่ยนแปลง และ down() function สำหรับการ rollback ถ้าเกิดปัญหา การที่ Migration เป็น Code ทำให้เราสามารถใส่ไว้ใน Git, Review, และทำงานเป็น Team ได้

ข้อดีของ Migrations:

หลังจากที่เราจัดการ Schema และ Performance แล้ว ยังมีอีกเรื่องสำคัญที่ห้ามลืม นั่นคือ Security โดยเฉพาะการป้องกัน SQL Injection ที่เป็นช่องโหว่อันตรายที่สุดอันดับต้นๆ


🛡️ Security — ป้องกัน SQL Injection

SQL Injection — อันตรายที่สุดของ web apps

// ❌ String concatenation (อย่าทำ!!!)
// User input: ' OR '1'='1' --
const query = `SELECT * FROM users
  WHERE email = '${email}'
  AND password = '${password}'`;

// กลายเป็น:
SELECT * FROM users
WHERE email = '' OR '1'='1' --'
AND password = 'anything'
// → Login ได้โดยไม่ต้องรู้ password!

// ✅ Parameterized queries (ทำแบบนี้เสมอ!)
const query = 'SELECT * FROM users WHERE email = $1 AND password = $2';
db.query(query, [email, password]);

ตัวอย่างข้างต้นแสดงความแตกต่างระหว่าง String Concatenation ที่อันตรายกับ Parameterized Queries ที่ปลอดภัย เมื่อใช้ Parameterized Queries Database จะแยก SQL Structure กับ Data ออกจากกัน ทำให้ไม่สามารถ Inject คำสั่ง SQL เพิ่มเติมได้

กฎทองของการป้องกัน SQL Injection:

นอกจาก Security แล้ว เรายังต้องรู้วิธีจัดการกับ Operations ที่ซับซ้อน ที่จำเป็นต้อง "ทำสำเร็จทั้งหมดหรือไม่ทำเลย" ซึ่งนั่นคือแนวคิดของ Database Transactions


🔄 Transactions — All or Nothing

-- Transaction — การสั่งซื้อต้อง atomic
-- ถ้า step ไหน fail → rollback ทั้งหมด!
BEGIN;

-- 1. สร้าง order
INSERT INTO orders (customer_id, status, total)
VALUES (1, 'confirmed', 54800.00)
RETURNING id INTO order_id;

-- 2. เพิ่ม order items
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES
    (order_id, 1, 1, 45900.00),
    (order_id, 3, 1, 8900.00);

-- 3. ลด stock
UPDATE products SET stock = stock - 1 WHERE id = 1;
UPDATE products SET stock = stock - 1 WHERE id = 3;

COMMIT;
-- ถ้าทุกอย่าง OK → COMMIT
-- ถ้ามี error → ROLLBACK อัตโนมัติ

ACID Properties:

ตัวอย่าง Transaction ข้างต้นแสดงการสั่งซื้อสินค้าที่ต้องทำหลายขั้นตอนพร้อมกัน ถ้าขั้นตอนใดล้มเหลว (เช่น สินค้าหมด, ข้อมูลผิด) ทุกอย่างจะถูก Rollback กลับสู่สถานะเดิม ป้องกันไม่ให้เกิด Inconsistent Data

ACID Properties ทำให้ Database เชื่อถือได้:

เมื่อเราเข้าใจ SQL พื้นฐานแล้ว คำถามต่อไปคือควรเขียน Raw SQL หรือใช้เครื่องมือช่วยเช่น ORM หรือ Query Builder มาดูเปรียบเทียบกันว่าแต่ละแบบเหมาะกับสถานการณ์ไหน


🏗️ ORM vs Query Builder vs Raw SQL

ORMQuery BuilderRaw SQL
ตัวอย่างPrisma, SQLAlchemy, TypeORMKnex.js, Kyselypg, psycopg2
Abstractionสูงกลางต่ำ
เรียนรู้ง่ายกลางต้องรู้ SQL
Complex queriesยากขึ้นยืดหยุ่น⭐ ยืดหยุ่นสุด
Type safety⭐ ดีมาก✅ OK❌ ไม่มี
Performanceดี (แต่ N+1 problem)⭐ ใกล้ raw⭐ เร็วสุด
เหมาะกับCRUD apps, rapid devComplex queriesAnalytics, optimization

การเปรียบเทียบข้างต้นแสดงให้เห็นว่าแต่ละ Approach มีจุดเด่นต่างกัน ORM เหมาะกับ Developer ที่ต้องการ Type Safety และ Productivity สูง แต่อาจมีข้อจำกัดใน Complex Queries และมีความเสี่ยงของ N+1 Query Problem

Query Builder เป็น Middle Ground ที่ดี มีความยืดหยุ่นใกล้เคียง Raw SQL แต่ยังมี Type Safety บางส่วน เหมาะกับ Team ที่มี SQL Skills ดีและต้องการควบคุม Queries มากขึ้น

Raw SQL ให้ Performance และความยืดหยุ่นสูงสุด เหมาะกับ Analytics, Reporting, หรือ Optimization ขั้นสูง แต่ต้องระวังเรื่อง SQL Injection และการ Maintain

💡 แนะนำ: Prisma/SQLAlchemy ORM สำหรับ CRUD ทั่วไป + Raw SQL สำหรับ complex analytics queries

เมื่อเราเข้าใจเทคนิคต่างๆ แล้ว สิ่งสำคัญคือการนำไปใช้อย่างเป็นระบบ Checklist ต่อไปนี้จะช่วยให้คุณออกแบบและจัดการ Database อย่างมืออาชีพ


📋 Database Design Checklist

📐Schema Design
[ ] Normalize ถึง 3NF
[ ] ใช้ meaningful table/column names
[ ] snake_case สำหรับ naming
[ ] ทุก table มี Primary Key
[ ] ใช้ Foreign Keys + ON DELETE
[ ] มี created_at, updated_at
Performance
[ ] Index บน Foreign Keys
[ ] Index บน columns ที่ใช้ WHERE/JOIN บ่อย
[ ] EXPLAIN ANALYZE queries ที่ช้า
[ ] ใช้ DECIMAL สำหรับเงิน (ไม่ใช่ FLOAT)
[ ] Pagination (LIMIT/OFFSET)
🛡️Security
[ ] Parameterized queries เสมอ
[ ] Hash passwords (bcrypt/argon2)
[ ] ไม่เก็บ sensitive data แบบ plaintext
[ ] ใช้ least-privilege DB users
[ ] Backup strategy (daily + point-in-time)
🔄Operations
[ ] ใช้ Migrations สำหรับทุก schema change
[ ] Connection pooling
[ ] Soft delete (is_active) แทน hard delete
[ ] Monitor slow queries

Checklist ข้างต้นครอบคลุมจุดสำคัญทุกด้านของการทำงานกับ Database ตั้งแต่การออกแบบ Performance การรักษาความปลอดภัย และการ Operations ที่ดี การทำตาม Checklist เหล่านี้จะช่วยให้ Database ของคุณมีคุณภาพและพร้อมรองรับการเติบโตของแอปพลิเคชัน

การออกแบบ Database ที่ดีไม่ใช่เรื่องที่ทำได้ในคืนเดียว แต่เป็นทักษะที่พัฒนาได้ผ่านการฝึกฝนและประสบการณ์ สิ่งสำคัญคือการเริ่มต้นด้วยหลักการที่ถูกต้อง และค่อยๆ ปรับปรุงเมื่อมีความเข้าใจมากขึ้น

จุดสำคัญที่ควรจำไว้คือ Database เป็น Foundation ของแอปพลิเคชัน การลงทุนเวลาทำความเข้าใจและออกแบบให้ดีตั้งแต่เริ่มต้นจะประหยัดเวลาและลดปัญหาในอนาคตได้มากมาย


🔑 Key Takeaways

  1. PostgreSQL → แนะนำเป็นตัวเลือกแรก — ทำได้ทุกอย่าง, JSONB, extensions, free
  2. Normalize ถึง 3NF → ลดข้อมูลซ้ำ, denormalize เมื่อ performance ต้องการ
  3. Index ทุก FK + WHERE columns → EXPLAIN ANALYZE เพื่อตรวจสอบ
  4. Parameterized queries เสมอ → ป้องกัน SQL Injection (ห้าม concat string!)
  5. Transactions → ACID = all or nothing, ใช้สำหรับ multi-step operations
  6. DECIMAL สำหรับเงิน → ห้ามใช้ FLOAT! (0.1 + 0.2 ≠ 0.3)
  7. Migrations → ทุก schema change เป็น code, version controlled
  8. ORM + Raw SQL → ORM สำหรับ CRUD, Raw SQL สำหรับ analytics
  9. Window Functions → RANK, SUM OVER, ROW_NUMBER — analytics ขั้นสูง
  10. Soft delete → อย่า DELETE จริง, ใช้ is_active = false
บทความจากซีรีส์ DevOps & Vibe Coding 2026
← Previous
Automated Testing with GitHub — ทดสอบอัตโนมัติทุก Push
Next →
Authentication & Authorization — ระบบยืนยันตัวตนและจัดการสิทธิ์อย่างปลอดภัย