Database Design
Schema design, normalization, indexing strategy, audit trail patterns สำหรับ financial/trading systems
What I Can Do
- ออกแบบ database schema ตั้งแต่ ERD จนถึง production-ready DDL
- Apply normalization (1NF–3NF) และรู้ว่าเมื่อไหร่ควร denormalize
- ออกแบบ indexing strategy ให้เหมาะกับ query patterns
- Design audit trail, soft delete, multi-tenancy patterns
- วางแผน schema migration ที่ไม่กระทบ production
Entity & Attribute
- Entity = สิ่งที่ต้องเก็บข้อมูล เช่น User, Order, Product → กลายเป็น table
- Attribute = คุณสมบัติของ entity เช่น email, status, price → กลายเป็น column
- เลือก data type ให้เหมาะกับ attribute เช่น
DECIMALสำหรับเงิน ไม่ใช่FLOAT
Primary Key & Foreign Key
- Primary Key — unique identifier ของแต่ละ row, ต้องไม่ซ้ำและไม่เป็น NULL
- Foreign Key — column ที่ reference primary key ของ table อื่น, enforce referential integrity
1-to-1 / 1-to-Many / Many-to-Many
- 1-to-1 — user ↔ profile (foreign key + UNIQUE constraint)
- 1-to-Many — user ↔ orders (foreign key ฝั่ง many)
- Many-to-Many — users ↔ roles (ใช้ junction table
user_roles)
ER Diagram Basics
Entity-Relationship Diagram แสดง entities, attributes, และ relationships — ใช้ตอนออกแบบเพื่อให้ทีมเห็นภาพรวมก่อนเขียน DDL
Data Types Selection
เลือก data type ให้เหมาะสม:
- เงิน →
DECIMAL(20,8)(ไม่ใช่ FLOAT เพราะ precision loss) - เวลา →
TIMESTAMPTZ(ไม่ใช่ TIMESTAMP เพราะไม่มี timezone) - ID →
UUID(distributed-friendly) หรือBIGSERIAL(simple, sequential) - สถานะ →
ENUMหรือVARCHAR+ CHECK
NOT NULL & DEFAULT
NOT NULL— force ให้ทุก row ต้องมีค่า, ป้องกัน data quality issuesDEFAULT NOW()— ให้ค่า default อัตโนมัติ ลด burden จาก application layer
Normalization (1NF, 2NF, 3NF)
- 1NF — ไม่มี repeating groups, แต่ละ column มี atomic value
- 2NF — 1NF + ทุก non-key column depend on whole primary key (ไม่ใช่แค่บางส่วน)
- 3NF — 2NF + ไม่มี transitive dependency (non-key column ไม่ depend on non-key column อื่น)
Denormalization Trade-offs
Denormalize เมื่อ read performance สำคัญกว่า write consistency — เช่น สร้าง summary table สำหรับ dashboard ที่ JOIN หลาย tables แพงเกินไป trade-off คือ data อาจ stale และ update ยากขึ้น
Indexing Basics (When & Why)
- สร้าง index บน columns ที่ใช้ใน
WHERE,JOIN,ORDER BYบ่อย - ไม่ index ทุก column — แต่ละ index เพิ่ม overhead ตอน INSERT/UPDATE
- ตรวจ query plan (
EXPLAIN) เพื่อยืนยันว่า index ถูกใช้จริง
Composite Keys
Primary key ที่ประกอบด้วยหลาย columns — เช่น PRIMARY KEY (user_id, currency) สำหรับ wallets table ที่แต่ละ user มี 1 wallet ต่อ currency
Junction Tables
ใช้แก้ many-to-many relationship — สร้าง table กลางที่มี foreign keys ไปยังทั้ง 2 tables เช่น user_roles (user_id, role_id)
Constraints (CHECK, UNIQUE)
CHECK (balance >= 0)— enforce business rules ที่ database levelUNIQUE (user_id, currency)— ป้องกัน duplicate records- Constraints เป็น safety net สุดท้าย ถึง application layer พลาดก็ไม่เข้า DB
Soft Delete Pattern
ใช้ deleted_at TIMESTAMPTZ แทนการ DELETE จริง — ข้อมูลยัง query ได้ (audit), กู้คืนง่าย ต้อง filter WHERE deleted_at IS NULL ในทุก query (หรือใช้ view)
Partitioning Strategies
แบ่ง table ใหญ่ออกเป็น partitions เพื่อ performance:
- Range — แบ่งตาม date (เช่น monthly partitions สำหรับ transactions)
- List — แบ่งตาม category/status
- Hash — กระจาย rows สม่ำเสมอ
Audit Trail Design
ทุก data change ต้อง track ได้ — สร้าง audit table ที่บันทึก who, what, when, before/after values ใช้ triggers หรือ application-level logging สำหรับ financial data ห้ามลบ audit records
Multi-tenancy Patterns
- Shared database, shared schema — ใช้
tenant_idcolumn ทุก table + Row Level Security - Shared database, separate schema — แต่ละ tenant มี schema ของตัวเอง
- Separate database — isolation สูงสุด แต่ manage ยาก
Schema Migration Strategies (Zero-downtime)
- Add column ก่อน (nullable), deploy code ที่ write ค่าใหม่, backfill, แล้วค่อย add NOT NULL
- ใช้
CREATE INDEX CONCURRENTLYเพื่อไม่ lock table - แยก migration เป็นหลาย steps เล็กๆ แทน 1 migration ใหญ่
UUID vs Auto-increment Trade-offs
- UUID — globally unique, ไม่ต้อง coordinate ระหว่าง servers, ไม่เดาได้ แต่ larger, random → B-tree fragmentation
- Auto-increment — compact, sequential → index-friendly แต่ need coordination, predictable
- UUIDv7 (time-sortable) เป็น compromise ที่ดี
Event Sourcing vs CRUD
- CRUD — store current state, update in-place — simple, familiar
- Event Sourcing — store events (immutable log), derive current state — full audit trail, temporal queries แต่ complexity สูงกว่า
CQRS Pattern
Command Query Responsibility Segregation — แยก write model (normalized, consistent) ออกจาก read model (denormalized, optimized for queries) ใช้เมื่อ read/write patterns ต่างกันมาก
Related Skills
- SQL — query language ที่ใช้กับ schema ที่ออกแบบ
- PostgreSQL — database engine ที่ implement schema
- Go — backend ที่ใช้ repository pattern เข้าถึง database