ซีรีส์ เขียนโปรแกรมพื้นฐาน · ตอนที่ 4 จาก 7
อ่าน SQL แค่ 4 คำ ก็ตรวจตัวเลขที่ AI ทำให้ได้
16 ต.ค. 2569 · อ่าน 13 นาที
AI สรุปยอดขายหรือทำแดชบอร์ดจากฐานข้อมูลให้ได้ แต่ตัวเลขนั้นนับอะไร และไม่นับอะไร? บทความนี้สอนอ่าน SELECT FROM WHERE GROUP BY ด้วยตารางยอดขายตัวอย่างที่รันจริง ชี้ความผิดพลาด 2 แบบที่ผิดแค่บรรทัดเดียวแต่ทำให้ยอดเพี้ยนไปเป็นพันบาท พร้อมเช็กลิสต์ก่อนเชื่อตัวเลขจาก AI

สมมติ AI สรุปยอดขายเดือนกันยายนจากข้อมูลตัวอย่างให้ ได้ 6,200 บาท ตัวเลขดูปกติและไม่มีอะไรเตือน แต่ยอดจริงคือ 8,450 บาท ส่วนที่หายไปเกิดจากบรรทัดเดียวในคำสั่งที่ AI เขียน
บทความนี้เป็นตอนที่ 4 ของซีรีส์ "เขียนโปรแกรมพื้นฐาน" สำหรับคนที่ให้ AI ทำโค้ดหรือตัวเลขให้ และอยากตรวจงานของ AI เป็น ตอนนี้จะอ่าน SQL แค่ 4 คำ ซึ่งพอให้รู้ว่าตัวเลขจากฐานข้อมูล นับอะไร และไม่นับอะไร
ข้อมูล ณ ตุลาคม 2569 ตัวอย่างทุกอันเรารันจริงใน SQLite 3.46.1 ด้วยข้อมูลสมมติที่แต่งขึ้นเอง
ทำไมต้องอ่าน SQL ทั้งที่ AI เขียนให้
SQL คือภาษาที่ใช้ถามข้อมูลจากฐานข้อมูลแบบตาราง เมื่อคุณถาม AI ว่า "เดือนนี้ขายได้เท่าไร" แล้ว AI ไปดึงข้อมูลจากฐานข้อมูล สิ่งที่ AI ส่งไปถามจริงๆ มักเป็นคำสั่ง SQL ซึ่งเรียกว่า query
เมื่อ 8 ตุลาคม 2569 Claude เปิดตัว Claude Dashboards (รุ่นเบต้า สำหรับแผนเสียเงิน) ให้เชื่อมข้อมูลบริษัท แล้วถามเป็นภาษาธรรมดาเพื่อให้ได้แดชบอร์ด Claude เขียนไว้ว่า ปกติการถามข้อมูลบริษัท "ต้องเปิด ticket หรือเขียน SQL" และในแดชบอร์ด กดตัวเลขไหนก็ได้เพื่อดู query เบื้องหลัง โดย Claude ทำฉบับแรกให้ ส่วน "คุณเป็นคนตัดสินว่าอะไรจะถูกแชร์"
คนที่รับผิดชอบตัวเลขจึงเป็นเรา และเรามองว่าหลักในบทความนี้ใช้ได้กับเครื่องมือที่เปิดดู query ได้
ข้อมูลตัวอย่าง: ยอดขายร้านสมมติ
ตาราง orders มี 12 รายการ (ข้อมูลสมมติที่แต่งขึ้นเอง)
| id | order_time | channel | amount | status |
|---|---|---|---|---|
| 1 | 2026-08-31 20:15 | shop | 500 | paid |
| 2 | 2026-09-01 09:30 | shop | 1200 | paid |
| 3 | 2026-09-03 14:00 | online | 800 | paid |
| 4 | 2026-09-07 11:45 | shop | 650 | cancelled |
| 5 | 2026-09-10 16:20 | online | 1500 | paid |
| 6 | 2026-09-15 10:05 | shop | 900 | paid |
| 7 | 2026-09-18 19:40 | online | 2000 | cancelled |
| 8 | 2026-09-22 13:10 | shop | 700 | paid |
| 9 | 2026-09-26 15:30 | online | 1100 | paid |
| 10 | 2026-09-30 18:20 | shop | 1300 | paid |
| 11 | 2026-09-30 20:45 | online | 950 | paid |
| 12 | 2026-10-01 08:10 | online | 600 | paid |
ความหมายของแต่ละคอลัมน์
order_timeคือวันเวลาที่สั่ง (ปี ค.ศ. 2026 ตรงกับ พ.ศ. 2569)channelคือช่องทาง shop คือหน้าร้าน online คือออนไลน์amountคือยอดเงินเป็นบาทstatusคือสถานะ paid คือจ่ายแล้ว cancelled คือยกเลิก
คำถามที่เราจะให้ฐานข้อมูลตอบคือ ยอดขายเดือนกันยายน แยกหน้าร้านกับออนไลน์ เฉพาะที่จ่ายแล้ว
4 คำที่ต้องอ่านใน query
query ที่ตอบคำถามนี้ได้ถูกต้องเป็นแบบนี้
SELECT channel, SUM(amount) AS total FROM orders WHERE order_time >= '2026-09-01' AND order_time < '2026-10-01' AND status = 'paid' GROUP BY channel;
เมื่อรัน ได้ผลดังนี้
+---------+-------+ | channel | total | +---------+-------+ | online | 4350 | | shop | 4100 | +---------+-------+
ยอดหน้าร้านคือ 4,100 บาท และยอดออนไลน์คือ 4,350 บาท ตารางข้างล่างอธิบายว่าแต่ละคำใน query นี้ทำหน้าที่อะไร
| คำ | ถามว่า | ในตัวอย่างนี้ |
|---|---|---|
| FROM | ข้อมูลมาจากตารางไหน | ตาราง orders |
| WHERE | แถวไหนถูกนับ | สั่งตั้งแต่ 1 ก.ย. แต่ก่อน 1 ต.ค. และจ่ายแล้ว |
| GROUP BY | แยกกลุ่มตามอะไร | แยกตามช่องทาง |
| SELECT | เอาอะไรออกมาแสดง | ชื่อช่องทาง กับผลรวม (SUM) ของ amount ตั้งชื่อว่า total |
อ่านตามลำดับที่ฐานข้อมูลทำ ไม่ใช่ลำดับที่เขียน
SELECT อยู่บรรทัดแรก แต่เอกสารของ SQLite อธิบายว่าฐานข้อมูลทำงานเป็นขั้น คือ หาข้อมูลจาก FROM แล้วกรองด้วย WHERE แล้วรวมกลุ่มตาม GROUP BY จากนั้นจึงคำนวณสิ่งที่ SELECT ขอ การอ่านตามลำดับนี้จะเข้าใจง่ายกว่า
WHERE คือคำที่ต้องดูก่อน
SQLite บอกว่า มีแค่แถวที่เงื่อนไขใน WHERE เป็นจริงเท่านั้นที่ได้ไปต่อ แถวที่ไม่ผ่านจะหายไปเงียบๆ โดยไม่มีอะไรเตือน เรามองว่าถ้าเลือกตารางผิดหรือคอลัมน์ผิด มักได้ตัวเลขแปลกจนสะดุดตา แต่ถ้า WHERE ผิดเพียงเล็กน้อย จะได้ตัวเลขที่ดูปกติมาก
GROUP BY: ผลรวมของทุกกลุ่มต้องเท่ากับยอดรวม
แถวที่มีค่าเหมือนกันจะอยู่กลุ่มเดียวกัน วิธีตรวจง่ายๆ คือบวกผลของทุกกลุ่ม ในตัวอย่างนี้ 4,350 บวก 4,100 ได้ 8,450 เราลองรันแบบตัด GROUP BY ออก ก็ได้ 8,450 เท่ากัน
ตัวเลขเพี้ยนได้อย่างไร: ความผิดพลาด 2 แบบใน WHERE
พลาดแบบที่ 1: ลืมกรองสถานะ
ลองตัดบรรทัด AND status = 'paid' ออกเพียงบรรทัดเดียว
SELECT channel, SUM(amount) AS total FROM orders WHERE order_time >= '2026-09-01' AND order_time < '2026-10-01' GROUP BY channel;
เมื่อรัน ได้ผลดังนี้
+---------+-------+ | channel | total | +---------+-------+ | online | 6350 | | shop | 4750 | +---------+-------+
ยอดรวมเป็น 11,100 บาท เกินจริง 2,650 บาท เพราะนับออเดอร์ที่ยกเลิกไปด้วย
วิธีจับความผิดพลาดนี้คือ ก่อนเชื่อยอด ให้ดูว่าตารางมีสถานะอะไรบ้าง ด้วย query นี้
SELECT status, COUNT(*) AS rows, SUM(amount) AS total FROM orders GROUP BY status;
เมื่อรัน ได้ผลดังนี้
+-----------+------+-------+ | status | rows | total | +-----------+------+-------+ | cancelled | 2 | 2650 | | paid | 10 | 9550 | +-----------+------+-------+
ตารางนี้มีสถานะ cancelled อยู่ query ที่ถูกต้องจึงต้องตัดรายการเหล่านี้ออก (query นี้ยังไม่กรองเดือน ตัวเลขจึงรวมเดือนอื่นด้วย)
พลาดแบบที่ 2: วันสุดท้ายของเดือนหายไป
ครั้งนี้เขียนช่วงวันที่ด้วย BETWEEN (แปลว่า "ระหว่าง") ซึ่งดูเป็นธรรมชาติมาก
SELECT channel, SUM(amount) AS total FROM orders WHERE order_time BETWEEN '2026-09-01' AND '2026-09-30' AND status = 'paid' GROUP BY channel;
เมื่อรัน ได้ผลดังนี้
+---------+-------+ | channel | total | +---------+-------+ | online | 3400 | | shop | 2800 | +---------+-------+
ยอดรวมเป็น 6,200 บาท หายไป 2,250 บาท ซึ่งคือ 2 ออเดอร์ของวันที่ 30 กันยายน
สาเหตุที่หายไปคือ เอกสารของ SQLite บอกว่า BETWEEN มีความหมายเท่ากับ "มากกว่าหรือเท่ากับ และน้อยกว่าหรือเท่ากับ" และ SQLite ไม่มีชนิดข้อมูลวันที่โดยเฉพาะ แต่เก็บวันที่เป็นข้อความแบบ YYYY-MM-DD HH:MM:SS ได้ ตัวอย่างนี้ก็เก็บแบบนั้น
เมื่อเทียบกันแบบข้อความ '2026-09-30 18:20' จะอยู่หลัง '2026-09-30' เหมือนในพจนานุกรมที่คำยาวกว่าซึ่งขึ้นต้นเหมือนกันจะอยู่ทีหลัง ออเดอร์ตอนเย็นวันที่ 30 จึง "เกิน" ขอบบนไป
วิธีจับความผิดพลาดนี้คือ ขอให้แสดงแถวแรก แถวสุดท้าย และจำนวนแถวที่ถูกนับ ด้วย query แบบนี้
SELECT MIN(order_time) AS first, MAX(order_time) AS last, COUNT(*) AS rows FROM orders WHERE order_time BETWEEN '2026-09-01' AND '2026-09-30' AND status = 'paid';
เมื่อรัน ได้ผลดังนี้
+------------------+------------------+------+ | first | last | rows | +------------------+------------------+------+ | 2026-09-01 09:30 | 2026-09-26 15:30 | 6 | +------------------+------------------+------+
รายการสุดท้ายที่ถูกนับคือวันที่ 26 ไม่ใช่ 30 เห็นแล้วรู้ทันทีว่าขอบบนผิด ถ้ารัน query แบบเดียวกันนี้กับ WHERE ที่ถูกต้อง จะได้รายการสุดท้ายเป็น 2026-09-30 20:45 และนับได้ 8 แถว
วิธีเขียนที่ปลอดภัยกว่าคือ ตั้งแต่วันแรกของเดือน และน้อยกว่าวันแรกของเดือนถัดไป แบบ query ที่ถูกต้องข้างบน
เราทดสอบกับ SQLite ที่เก็บวันที่เป็นข้อความเท่านั้น ฐานข้อมูลที่มีชนิดวันที่โดยเฉพาะอาจให้ผลต่างไป แต่เรามองว่าการตรวจด้วยแถวแรก แถวสุดท้าย และจำนวนแถว ใช้ได้กับทุกฐานข้อมูล
เทียบผลของ query ทั้ง 3 แบบ
| query | หน้าร้าน | ออนไลน์ | รวม |
|---|---|---|---|
| ถูกต้อง | 4,100 | 4,350 | 8,450 |
| ลืมกรองสถานะ | 4,750 | 6,350 | 11,100 (เกิน 2,650) |
| ใช้ BETWEEN กับวันที่ที่มีเวลา | 2,800 | 3,400 | 6,200 (ขาด 2,250) |
ทั้ง 3 แบบรันผ่านโดยไม่มี error เลย
เช็กลิสต์ 6 ข้อ ก่อนเชื่อตัวเลขจาก AI
- FROM ใช้ตารางถูกต้องหรือไม่ เช่น ตารางออเดอร์จริง ไม่ใช่ตารางทดสอบหรือตารางใบเสนอราคา
- WHERE ช่วงวันที่ วันแรกและวันสุดท้ายตรงกับที่ถามหรือไม่ ขอบปลายควรเป็น "น้อยกว่าวันแรกของเดือนถัดไป"
- WHERE สถานะ รายการยกเลิก คืนเงิน หรือรายการทดสอบ ถูกตัดออกหรือไม่ ให้ดูก่อนว่าตารางมีสถานะอะไรบ้าง
- GROUP BY แยกกลุ่มตามที่ถามจริงหรือไม่ และผลรวมของทุกกลุ่มเท่ากับยอดรวมหรือไม่
- SELECT เลือกใช้
SUM(รวมยอด)COUNT(นับรายการ) หรือAVG(ค่าเฉลี่ย) ถูกตัวหรือไม่ และใช้คอลัมน์ถูกหรือไม่ เช่น ยอดก่อนหรือหลังหักส่วนลด - ตรวจกลับ ขอ query ที่แสดงแถวแรก แถวสุดท้าย และจำนวนแถวที่ถูกนับ แล้วเทียบกับตัวเลขที่รู้อยู่แล้ว เช่น ยอดจากระบบขายหรือบัญชี
⚠️ ใช้ query แบบอ่านอย่างเดียว ถ้าเห็นคำว่า UPDATE DELETE INSERT หรือ DROP ใน query ที่ AI ให้มา ให้หยุดและถามก่อน เพราะคำเหล่านี้แก้ เพิ่ม หรือลบข้อมูล ไม่ได้แค่อ่าน ถ้าจะลองรัน query เอง ให้รันกับสำเนาข้อมูล หรือใช้บัญชีที่มีสิทธิ์อ่านอย่างเดียว ไม่ใช่รันกับฐานข้อมูลจริงที่ใช้งานอยู่ เอกสารของ SQLite เขียนไว้ว่า DELETE ที่ไม่มี WHERE จะลบทุกแถวในตาราง
คำสั่งสำเร็จรูปให้ AI อธิบาย query
วางคำสั่งนี้ แล้วใส่ query ที่ AI ให้มาไว้ท้ายสุด
อธิบาย query นี้เป็นภาษาไทยทีละส่วน ตามลำดับ FROM → WHERE → GROUP BY → SELECT - FROM: ดึงจากตารางไหน - WHERE: เงื่อนไขแต่ละข้อทำให้แถวแบบไหน "ไม่ถูกนับ" ยกตัวอย่างแถวละ 1 แบบ - ช่วงวันที่: วันเวลาแรกและวันเวลาสุดท้ายที่ถูกนับจริงคืออะไร - GROUP BY: แยกกลุ่มตามอะไร - SELECT: ตัวเลขที่ได้คือผลรวม การนับ หรือค่าเฉลี่ย ของคอลัมน์ไหน จากนั้นเขียน query ตรวจกลับ ที่แสดงแถวแรก แถวสุดท้าย และจำนวนแถวที่ถูกนับ ใช้ SELECT เท่านั้น ห้ามแก้หรือลบข้อมูล (วาง query ที่นี่)
แต่ละส่วนของคำสั่งช่วยอะไร
- การเรียงตามลำดับที่เริ่มจาก FROM แล้วจึง WHERE ทำให้คำอธิบายเรียงตามที่ฐานข้อมูลทำงานจริง
- การถามว่าแถวแบบไหน "ไม่ถูกนับ" บังคับให้ AI พูดถึงสิ่งที่หายไป ซึ่งคือที่มาของตัวเลขเพี้ยน
- query ตรวจกลับคือสิ่งที่เรารันเองแล้วเห็นผลจริง
คำอธิบายของ AI ผิดได้ สิ่งที่ต้องเชื่อคือผลของ query ตรวจกลับ
ถ้าเจอคำอื่นนอกจาก 4 คำนี้
- JOIN เอาหลายตารางมารวมกัน ให้ระวังการนับซ้ำ เราลองแล้วว่า ถ้าออเดอร์หนึ่งมีสินค้า 2 ชิ้นในอีกตาราง แล้วรวมยอดออเดอร์หลัง JOIN ยอดของออเดอร์นั้นจะถูกนับ 2 รอบ ถ้าเห็น JOIN ให้ถาม AI ว่า "ออเดอร์เดียวถูกนับซ้ำได้ไหม"
- HAVING กรองข้อมูลหลังรวมกลุ่มแล้ว เช่น เอาเฉพาะช่องทางที่ยอดเกิน 4,200 (ในตัวอย่างนี้เหลือแค่ online) อ่านแบบเดียวกับ WHERE แต่ใช้กับกลุ่ม
- ORDER BY แค่เรียงลำดับ ไม่เปลี่ยนตัวเลข SQLite บอกว่าถ้าไม่มี ORDER BY ลำดับแถวจะไม่แน่นอน ถ้าคุณลองรันเองแล้วบรรทัดสลับกับในบทความ ให้เทียบที่ตัวเลขเป็นหลัก
สรุป
อ่าน 4 คำตามลำดับที่ฐานข้อมูลทำงาน คือ FROM, WHERE, GROUP BY แล้วจึง SELECT และใช้เวลาดู WHERE ให้นานที่สุด ช่วงวันที่ต้องครอบคลุมวันสุดท้ายทั้งวัน สถานะที่ไม่ควรนับต้องถูกตัดออก และขอ query ตรวจกลับที่บอกแถวแรก แถวสุดท้าย และจำนวนแถวทุกครั้ง
ถ้าอยากรู้ว่าฐานข้อมูลแบบตารางต่างจากแบบอื่นอย่างไร อ่านต่อได้ที่ ฐานข้อมูลและที่เก็บไฟล์: ข้อมูลของเว็บอยู่ที่ไหน (สำหรับสมาชิกฟรี) ส่วนตอนถัดไปในซีรีส์ เราจะดูโปรแกรมที่รันค้างอยู่เบื้องหลัง
อาทิตย์ละสกิล เดี๋ยวก็เก่ง