🛑 1. The Problem First: "Subquery นรก" (The Pyramid of Doom)
ลองนึกถึงวันที่คุณต้องดึงข้อมูลสรุปยอดขายที่ซับซ้อน แล้วโค้ด SQL ของคุณออกมาหน้าตาแบบนี้:
-- ❌ Naive Approach: Subquery ซ้อนกันจนอ่านไม่ออก
SELECT * FROM (
SELECT user_id, total FROM (
SELECT user_id, SUM(amount) as total FROM orders GROUP BY user_id
) as t1 WHERE total > 1000
) as t2 JOIN users ON t2.user_id = users.id;
-- 🌋 พัง! แค่เห็นก็ปวดหัวแล้ว ถ้าต้อง Debug หรือเพิ่มเงื่อนไข คุณจะหาจุดเริ่มไม่เจอ
ปัญหา: ยิ่ง Business Logic ซับซ้อน SQL ของคุณจะยิ่งยาวและซ้อนกันหลายชั้น สิ่งนี้ทำให้การบำรุงรักษา (Maintenance) เป็นเรื่องยากมาก และที่ร้ายกว่านั้นคือ ประสิทธิภาพ (Performance) อาจจะแย่ลงเพราะ Browser/DB Optimizer ต้องประมวลผล Subquery ซ้ำซ้อน นี่คือที่มาของ Technical Debt ในระดับ Database ครับ
💡 2. Real-Life Analogy: การต่อจิ๊กซอว์ vs การวางกองขยะ
- Subqueries: เหมือนการ "ยัดทุกอย่างลงไปในถุงเดียว". คุณต้องรื้อถุงชั้นในสุดออกมาดูถึงจะรู้ว่าข้างในคืออะไร
- CTE (WITH): เหมือนการ "จัดของใส่กล่องติดป้ายกำกับ". คุณเตรียมกล่องที่ 1 (UserSales), กล่องที่ 2 (TopCustomer) แล้วค่อยเอาของในกล่องมาวางเรียงกันตอนจบ (Step-by-step)
- Window Functions: เหมือนการ "ถือไม้บรรทัดวัดระยะสะสม". คุณไม่ต้องหยิบของออกมานับใหม่ทีละชิ้น คุณแค่ลากไม้บรรทัดผ่านกองของ แล้วบันทึกยอดสะสมไปด้วยในตัว (Running Total)
🚀 3. Execution Journey: ขั้นตอนการดึงข้อมูลแบบ Senior
เราจะใช้ CTE เพื่อให้อ่านง่าย และ Window Functions เพื่อคำนวณข้ามบรรทัด
🛠 Step-by-step:
- Define CTE: สร้างตารางชั่วคราวที่มีชื่อเรียกชัดเจน (เหมือนประกาศตัวแปรใน JS)
- Apply Window Function: ใช้
OVER()เพื่อคำนวณ Ranking หรือ Sum โดยไม่ต้องใช้GROUP BYที่โหดร้าย - Consolidate: รวมข้อมูลจาก CTE ทั้งหมดมาเป็นผลลัพธ์สุดท้าย
// ✅ Best Practice: ใช้ CTE และ Window Function เพื่อความคลีน
WITH MonthlySales AS (
SELECT
product_name,
category,
amount,
-- 🛠 ใช้ Window Function หา Ranking ในแต่ละหมวดหมู่
ROW_NUMBER() OVER(PARTITION BY category ORDER BY amount DESC) as rank
FROM sales
)
SELECT * FROM MonthlySales WHERE rank <= 3; // 🛠 ดึงแค่ Top 3 ของทุกหมวดหมู่ จบในที่เดียว!
🪤 4. The Junior Trap: โรค "Self-Join เพื่อหาผลรวม"
จูเนียร์มักจะใช้วิธี Join ตารางตัวเองซ้ำๆ เพื่อหาค่าสะสมหรือค่าเฉลี่ย:
-- ❌ Junior Trap: กินทรัพยากร DB มหาศาล
SELECT s1.date, s1.amount, (SELECT SUM(amount) FROM sales s2 WHERE s2.date <= s1.date) as total
FROM sales s1;
-- 🌋 พัง! ถ้ามีข้อมูล 1 ล้านบรรทัด DB จะต้องวนลูปคำนวณวนไปวนมาเป็นล้านๆ ครั้ง (O(N^2))
ระวัง: การทำ Subquery ใน SELECT clause คือการฆ่าตัวตายในระดับ SQL
✅ การแก้ไข: ใช้ Window Function SUM(amount) OVER (ORDER BY date) ซึ่งทำงานได้เร็วกว่าหลักพันเท่าเพราะมันวนลูปแค่รอบเดียว (O(N))
⚖️ 5. The Why Matrix: Subquery vs CTE vs Window Func
| หัวข้อ | Subquery (แบบเก่า) | CTE (แบบใหม่) | Window Function (ขั้นเทพ) |
|---|---|---|---|
| ความอ่านง่าย | 🐢 ยากนรกแตก | ⚡⚡⚡ เหมือนอ่านนิยาย | ⚡⚡ กระชับมาก |
| Performance | ต่ำ (ถ้าซ้อนเยอะ) | ปานกลาง/สูง (ขึ้นอยู่กับ DB) | 🚀 สูงสุด สำหรับงานคำนวณข้ามแถว |
| ความยืดหยุ่น | ต่ำ | ⚡⚡⚡ สูงมาก (เรียกใช้ซ้ำได้) | ⚡⚡ สูง (ทำ Ranking ง่าย) |
| เหมาะกับ | งาน Simple CRUD | งาน Logic ซับซ้อนหลายชั้น | งาน Analytics / Reporting |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "SQL ไม่ใช่แค่ภาษาสำหรับดึงข้อมูล แต่เป็นพลังในการประมวลผลที่ทรงพลังที่สุด". การย้ายลอจิกบางส่วนมาคำนวณที่ Database ด้วยท่าที่สง่างาม ไม่เพียงแต่จะทำให้โค้ดแอปของคุณสะอาดขึ้น แต่ยังทำให้ระบบของคุณรวดเร็วปานกามนิตหนุ่มครับ!