หนึ่งในกับดักที่นักพัฒนา SQL ทั้งมือใหม่และมือเก๋าอาจเคยเจอ คือการที่ผลลัพธ์จากการคิวรี่ไม่เป็นไปตามที่คาดหวัง โดยเฉพาะเมื่อต้องจัดการกับคอลัมน์ที่มีค่า NULL
ลองจินตนาการถึงสถานการณ์นี้
เรามีตารางชื่อ orders ที่เก็บข้อมูลคำสั่งซื้อสินค้า และมีคอลัมน์ shipped_date เพื่อบอกวันที่สินค้าถูกจัดส่ง ถ้า shipped_date เป็น NULL แสดงว่าสินค้านั้นยังไม่ถูกจัดส่ง
ตาราง orders
| Column 1 | Column 2 |
|---|---|
สมมติว่าเราต้องการหารายการสั่งซื้อทั้งหมดที่ ไม่ได้ถูกจัดส่งในวันที่ 2025-07-11 โดยเราอาจจะเขียนคิวรี่ง่ายๆ แบบนี้:
-- คิวรี่ที่ดูเหมือนจะถูกต้อง
SELECT order_id, product_name, shipped_date
FROM orders
WHERE shipped_date != '2025-07-11';เราคาดหวังว่าจะได้ผลลัพธ์ 3 แถว คือ order_id 101 (ส่งวันอื่น), 103 (ยังไม่ส่ง) และ 105 (ยังไม่ส่ง) แต่ผลลัพธ์ที่ได้กลับมามีเพียงแถวเดียว:
| Column 1 | Column 2 |
|---|---|
เกิดอะไรขึ้น? ทำไมรายการที่ยังไม่ถูกส่ง (shipped_date เป็น NULL) ถึงหายไป?
หัวใจของปัญหานี้คือความเข้าใจผิดเกี่ยวกับ NULL ในโลกของฐานข้อมูล NULL ไม่ใช่ค่าที่เท่ากับ 0, ไม่ใช่ช่องว่าง, และไม่ใช่ "ไม่มีอะไร" แต่มันคือ สถานะ (state) ที่บ่งบอกว่า "ไม่ทราบค่า" (unknown) หรือ "ไม่มีข้อมูล" (missing value)
ด้วยเหตุนี้ ระบบจัดการฐานข้อมูลจึงใช้ตรรกะแบบสามค่า (Three-Valued Logic หรือ 3VL) ซึ่งประกอบด้วย:
- TRUE (จริง)
- FALSE (เท็จ)
- UNKNOWN (ไม่ทราบ)
เมื่อคุณนำ NULL ไปเปรียบเทียบกับค่าใดๆ ด้วยโอเปอเรเตอร์ทางตรรกะ (=, !=, <, >) ผลลัพธ์ที่ได้จะเป็น UNKNOWN เสมอ
- NULL = '2025-07-11' → UNKNOWN
- NULL != '2025-07-11' → UNKNOWN
- NULL = NULL → UNKNOWN
เงื่อนไข WHERE ใน SQL จะดึงข้อมูลเฉพาะแถวที่ผลลัพธ์ของเงื่อนไขเป็น TRUE เท่านั้น มันจะทิ้งแถวที่ผลลัพธ์เป็น FALSE และ UNKNOWN ไปทั้งหมด นี่คือสาเหตุที่แถว order_id 103 และ 105 หายไปจากผลลัพธ์ของเรา
วิธีแก้ไข: จัดการ NULL อย่างชัดเจน
เพื่อให้ได้ผลลัพธ์ตามที่ต้องการ เราต้องเขียนเงื่อนไขเพื่อจัดการกับ NULL โดยเฉพาะ เป็นวิธีที่ตรงไปตรงมาที่สุด คือการเพิ่มเงื่อนไขเข้าไปว่า "หรือค่าในคอลัมน์นั้นต้องเป็น NULL"
SELECT order_id, product_name, shipped_date
FROM orders
WHERE (shipped_date != '2025-07-11' OR shipped_date IS NULL);ผลลัพธ์ที่ถูกต้อง:
| Column 1 | Column 2 |
|---|---|
ตอนนี้คิวรี่ทำงานถูกต้องแล้ว เพราะมันเลือกแถวที่ shipped_date ไม่ใช่วันที่ '2025-07-11' หรือ แถวที่ shipped_date เป็น NULL
วิธีที่ 2: ใช้ฟังก์ชัน COALESCE
COALESCE เป็นฟังก์ชันที่มีประโยชน์มากในการจัดการ NULL มันจะคืนค่าแรกที่ไม่ใช่ NULL จากรายการที่เราส่งเข้าไป เราสามารถใช้มันเพื่อแปลง NULL ให้เป็นค่าอื่นชั่วคราวเพื่อการเปรียบเทียบได้
-- กำหนดให้ NULL มีค่าเทียบเท่ากับวันที่ในอดีตไปเลย
SELECT order_id, product_name, shipped_date
FROM orders
WHERE COALESCE(shipped_date, '1900-01-01'::date) != '2025-07-11'::date;ในคิวรี่นี้ ถ้า shipped_date เป็น NULL มันจะถูกแทนที่ด้วยวันที่ '1900-01-01' ก่อนนำไปเปรียบเทียบ ทำให้เงื่อนไข '1900-01-01' != '2025-07-11' เป็น TRUE และได้ผลลัพธ์ที่ถูกต้องเช่นกัน