ให้ AI ช่วยเขียน SQL และอ่าน EXPLAIN

SQL เป็นงานที่ AI ช่วยได้ดีมาก ตั้งแต่เขียน query รายงานที่ join หลายตาราง ไปจนถึงอธิบายผล EXPLAIN ที่อ่านยาก แต่ SQL ก็เป็นงานที่ผิดพลาดแล้วเสียหายได้รุนแรง query ที่ดูถูกต้องอาจคืนตัวเลขผิดแบบเงียบ ๆ ส่วนคำสั่ง UPDATE หรือ DELETE ที่เงื่อนไขพลาดนิดเดียวอาจทำข้อมูลหายทั้งตาราง บทความนี้จะพาดูวิธีใช้ AI เขียน SQL สำหรับ PostgreSQL อย่างปลอดภัย ตั้งแต่การให้ schema จริง การตรวจผลกับข้อมูลจริง การอ่าน EXPLAIN ANALYZE การเลือก index และข้อควรระวังกับคำสั่งที่แก้หรือลบข้อมูล
ให้ schema จริง ไม่ใช่ให้ AI เดา
ถ้าเราสั่งแค่ "เขียน query หายอดขายรายเดือนของลูกค้าแต่ละคน" AI จะเดาชื่อตารางและคอลัมน์เอง เช่น orders.amount ทั้งที่ระบบเราใช้ sale_order.grand_total ผลคือได้ query ที่รันไม่ได้ หรือแย่กว่านั้นคือรันได้แต่ใช้คอลัมน์ผิดความหมาย
วิธีดึง schema ของตารางที่เกี่ยวข้องใน PostgreSQL:
# ดูโครงสร้างตารางใน psql (ชื่อคอลัมน์ type index foreign key)
psql -d app_dev -c '\d sale_order'
# ส่งออกเฉพาะ DDL ของตารางที่ต้องการ ไม่รวมข้อมูล
pg_dump -d app_dev --schema-only -t sale_order -t sale_order_item -t customer
สิ่งที่ควรแนบไปพร้อม schema:
- ความหมายของคอลัมน์ที่กำกวม เช่น
statusมีค่าอะไรบ้าง ค่าไหนนับเป็นยอดขาย - กติกาทางธุรกิจ เช่น ยอดขายนับจากวันที่ชำระเงิน ไม่ใช่วันที่สั่ง และไม่นับคำสั่งซื้อที่ถูกยกเลิก
- ปริมาณข้อมูลโดยประมาณ ตารางหลักพันแถวกับหลายสิบล้านแถวต้องเขียนต่างกัน
- เวอร์ชัน PostgreSQL เพราะบาง syntax มีเฉพาะเวอร์ชันใหม่
อย่าแนบข้อมูลจริงของลูกค้าไปด้วย ถ้าต้องการตัวอย่างข้อมูล ให้ใช้ข้อมูลสมมติหรือข้อมูลที่ปิดบังแล้ว
ไม่ดี:
เขียน SQL หายอดขายรายเดือนของลูกค้า
ดี:
PostgreSQL (schema ด้านล่าง)
เขียน query หายอดขายรวมต่อลูกค้าต่อเดือน ของปี 2025
- ยอดขาย = sum(sale_order.grand_total) เฉพาะ status = 'paid'
- เดือนนับจาก paid_at (timestamptz) ตามเวลา Asia/Bangkok
- ลูกค้าที่ไม่มียอดในเดือนนั้นไม่ต้องแสดง
- ใช้ placeholder แทนค่าคงที่ (เช่น :year)
อธิบายแต่ละส่วนของ query สั้น ๆ และบอกว่าควรมี index อะไร
[แนบ DDL]
ตรวจผลกับข้อมูลจริงเสมอ
query ที่รันผ่านไม่ได้แปลว่าถูก ข้อผิดพลาดที่ AI ทำบ่อยและมองไม่เห็นจากการอ่านโค้ด:
- JOIN ทำให้แถวซ้ำ เช่น join
sale_orderกับsale_order_itemแล้ว sumgrand_totalยอดจะถูกนับซ้ำตามจำนวนรายการสินค้า - LEFT JOIN กลายเป็น INNER JOIN เมื่อใส่เงื่อนไขของตารางขวาไว้ใน
WHEREแทนในON - NULL ใน NOT IN ถ้า subquery มีค่า NULL แม้แถวเดียว
NOT INจะไม่คืนแถวใดเลย ควรใช้NOT EXISTS - Timezone การ
date_truncบนtimestamptzโดยไม่ระบุโซนเวลา ทำให้ยอดช่วงหัวค่ำวันสิ้นเดือนไปอยู่ผิดเดือน
วิธีตรวจที่ได้ผล: เลือกกรณีเล็ก ๆ ที่คำนวณด้วยมือได้ เช่น ลูกค้าหนึ่งรายในหนึ่งเดือน แล้วเทียบผลของ query กับการนับด้วยวิธีอื่น ถ้าตรงกันในหลายกรณี รวมทั้งกรณีขอบ เช่น เดือนที่ไม่มีข้อมูลหรือคำสั่งซื้อที่ถูกยกเลิก จึงค่อยเชื่อ
SELECT c.id, c.name,
date_trunc('month', o.paid_at AT TIME ZONE 'Asia/Bangkok') AS month,
sum(o.grand_total) AS total
FROM sale_order o
JOIN customer c ON c.id = o.customer_id
WHERE o.status = 'paid'
AND o.paid_at >= make_timestamptz(:year, 1, 1, 0, 0, 0, 'Asia/Bangkok')
AND o.paid_at < make_timestamptz(:next_year, 1, 1, 0, 0, 0, 'Asia/Bangkok')
GROUP BY c.id, c.name, month
ORDER BY c.id, month;
สังเกตว่าเงื่อนไขช่วงวันที่เขียนบนคอลัมน์ paid_at ตรง ๆ ไม่ได้ครอบด้วยฟังก์ชัน ทำให้ใช้ index ได้ และใน PHP ให้ส่งค่าผ่าน parameter binding เสมอ:
$rows = Yii::$app->db->createCommand($sql, [
':year' => (int) $year,
':next_year' => (int) $year + 1,
])->queryAll();
อ่าน EXPLAIN ANALYZE ด้วยกันกับ AI
EXPLAIN แสดงแผนที่ PostgreSQL วางไว้ว่าจะรัน query อย่างไร ส่วน EXPLAIN ANALYZE รัน query จริงแล้วแสดงเวลาและจำนวนแถวจริงของแต่ละขั้น ตัวเลือก BUFFERS ช่วยให้เห็นการอ่านข้อมูลจาก memory และ disk
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM sale_order
WHERE customer_id = 1024 AND status = 'paid'
ORDER BY paid_at DESC
LIMIT 20;
ข้อควรระวัง: EXPLAIN ANALYZE รันคำสั่งจริง ถ้าเป็น UPDATE หรือ DELETE ข้อมูลจะถูกแก้จริง ให้ครอบด้วย transaction แล้ว rollback:
BEGIN;
EXPLAIN ANALYZE UPDATE sale_order SET status = 'archived' WHERE paid_at < '2020-01-01';
ROLLBACK;
สิ่งที่ควรดูในผล EXPLAIN และให้ AI ช่วยอธิบาย:
- Seq Scan บนตารางใหญ่ ที่มีเงื่อนไขกรองเหลือไม่กี่แถว มักเป็นสัญญาณว่าขาด index
- ค่า rows ที่ประมาณ (estimated) ห่างจาก rows จริง (actual) มาก แปลว่าสถิติของตารางอาจล้าสมัย ลอง
ANALYZE sale_order; - Sort ที่ใช้ disk (เห็นคำว่า external merge) แปลว่าข้อมูลที่ต้องเรียงใหญ่เกิน memory ที่กำหนด
- Nested Loop ที่วนหลายแสนรอบ ดูจากค่า loops
prompt ที่ใช้: แนบ query, ผล EXPLAIN ANALYZE ทั้งหมด, DDL และ index ที่มีอยู่ แล้วถาม "ขั้นไหนใช้เวลามากที่สุด เพราะอะไร และเสนอทางแก้ 2-3 แบบพร้อมข้อเสียของแต่ละแบบ" อย่าส่งแค่ query แล้วถามว่า "ทำให้เร็วขึ้น" เพราะ AI จะเดาจากรูปแบบทั่วไปโดยไม่รู้ว่าคอขวดจริงอยู่ที่ไหน
Index: เพิ่มเท่าที่จำเป็น
จาก query ตัวอย่างข้างบน index ที่เหมาะคือ composite index ที่เรียงตามคอลัมน์ในเงื่อนไขเท่ากับก่อน แล้วตามด้วยคอลัมน์ที่ใช้เรียง:
CREATE INDEX CONCURRENTLY idx_sale_order_customer_status_paid
ON sale_order (customer_id, status, paid_at DESC);
ข้อควรรู้เมื่อ AI เสนอ index:
- index ทุกตัวทำให้ INSERT และ UPDATE ช้าลงและใช้พื้นที่เพิ่ม อย่าเพิ่มทุกตัวที่ AI เสนอ ให้วัดผลก่อนและหลังด้วย EXPLAIN ANALYZE
- บน production ใช้
CREATE INDEX CONCURRENTLYเพื่อไม่ล็อกการเขียนตารางนาน ๆ (คำสั่งนี้รันภายใน transaction block ไม่ได้ ใน migration ของ Yii2 ต้องจัดการให้รันนอก transaction) - ถ้าค้นด้วยเงื่อนไขคงที่บ่อย ๆ เช่น
status = 'paid'ลองพิจารณา partial index - ตรวจ index ที่มีอยู่ก่อน อาจมี index ที่ครอบคลุมอยู่แล้ว
ระวังคำสั่งแก้และลบข้อมูล
ห้ามรัน UPDATE, DELETE, TRUNCATE หรือ ALTER ที่ AI เขียนให้บน production โดยตรงโดยไม่ผ่านขั้นตอนตรวจสอบ ขั้นตอนที่ปลอดภัย:
- เขียนเป็น
SELECTด้วยเงื่อนไขเดียวกันก่อน ดูจำนวนแถวและตัวอย่างแถวที่จะโดนผลกระทบ - รันบนฐาน dev หรือ staging ที่มีข้อมูลใกล้เคียงจริง
- สำรองข้อมูลก่อน อย่างน้อยตารางที่จะถูกแก้
- รันใน transaction ตรวจจำนวนแถวที่เปลี่ยน แล้วค่อย
COMMIT
-- 1. ตรวจก่อนว่าจะโดนกี่แถว
SELECT count(*) FROM sale_order WHERE status = 'pending' AND created_at < now() - interval '30 days';
-- 2. แก้ใน transaction
BEGIN;
UPDATE sale_order SET status = 'expired'
WHERE status = 'pending' AND created_at < now() - interval '30 days';
-- psql จะแสดง UPDATE <จำนวนแถว> ตรวจว่าตรงกับขั้นที่ 1
COMMIT; -- ถ้าไม่ตรง ให้ใช้ ROLLBACK;
อีกด่านป้องกันคือให้เครื่องมือ AI ที่เชื่อมฐานข้อมูลได้ใช้ database user ที่มีสิทธิ์แค่ SELECT เท่านั้น
สรุป
- แนบ DDL จริง ความหมายของคอลัมน์ และกติกาทางธุรกิจทุกครั้ง อย่าให้ AI เดาชื่อตาราง
- query ที่รันผ่านอาจผิดแบบเงียบ ๆ ตรวจกับข้อมูลจริงในกรณีที่คำนวณด้วยมือได้ และระวัง JOIN ซ้ำ NULL และ timezone
- ใช้ EXPLAIN (ANALYZE, BUFFERS) หาคอขวดจริงแล้วให้ AI ช่วยอธิบาย และครอบคำสั่งแก้ข้อมูลด้วย transaction ที่ rollback
- เพิ่ม index เท่าที่วัดแล้วได้ผล และใช้ CONCURRENTLY บน production
- คำสั่งแก้หรือลบข้อมูลต้อง SELECT ดูก่อน ทดสอบบน dev สำรองข้อมูล และรันใน transaction
ลองทำ
เลือก query ที่ช้าที่สุดในโปรเจกต์ของคุณ (ดูได้จาก log หรือส่วนขยาย pg_stat_statements ถ้าเปิดไว้) รัน EXPLAIN (ANALYZE, BUFFERS) บนฐาน dev แล้วส่งผลพร้อม DDL ให้ AI อธิบายว่าขั้นไหนช้าที่สุดและเสนอทางแก้ ลองทำตามข้อเสนอทีละข้อ วัดเวลาก่อนและหลัง และตรวจว่าผลลัพธ์ของ query ยังเหมือนเดิมทุกแถว
ความคิดเห็น (0)
ยังไม่มีความคิดเห็น — เริ่มคุยเป็นคนแรก ถามสิ่งที่สงสัย หรือแชร์ประสบการณ์ของคุณ