【บทช่วยสอน SQL NULL】เชี่ยวชาญการดึงข้อมูลด้วย IS NULL และ IS NOT NULL
เมื่อคุณเริ่มเรียนรู้ SQL คุณจะพบกับสิ่งที่น่าพิศวงอย่างหลีกเลี่ยงไม่ได้ นั่นคือ `NULL` มันถูกใช้เพื่อแสดงถึงสถานะที่ไม่มีข้อมูลอยู่ เช่น "ผู้ใช้ที่ไม่ได้ลงทะเบียนหมายเลขโทรศัพท์" หรือ "สินค้าที่ไม่มีอะไรเขียนอยู่ในช่องหมายเหตุ" อย่างไรก็ตาม การจัดการกับ `NULL` นั้นพิเศษมาก และผู้เริ่มต้นหลายคนมักจะเจอกับกำแพงที่ว่า "ทำไมฉันดึงข้อมูลที่ต้องการไม่ได้..."
สาเหตุที่ใหญ่ที่สุดคือการพยายามเปรียบเทียบ `NULL` ด้วยเครื่องหมาย = (เท่ากับ) ราวกับว่ามันเป็นสิ่งเดียวกับ "0" หรือ "สตริงว่าง ('')" ในโลกของ SQL มีกฎเชิงปรัชญาอยู่ว่า `NULL` ไม่เท่ากับค่าใดๆ แม้กระทั่งกับตัวเอง
บทความนี้จะอธิบายอย่างละเอียดถึงตัวตนของ `NULL` และตัวดำเนินการเฉพาะสำหรับจัดการกับมันอย่างถูกต้อง นั่นคือ `IS NULL` และ `IS NOT NULL` โดยใช้โค้ดที่คุณสามารถคัดลอกและนำไปใช้ได้ทันที ไม่เกินเลยที่จะกล่าวว่าผู้ที่เชี่ยวชาญ `NULL` ก็คือผู้เชี่ยวชาญ SQL มาใช้โอกาสนี้ฝึกฝนทักษะการตรวจสอบการมีอยู่ของข้อมูลให้เชี่ยวชาญกันเถอะ!
การเตรียมตัว: มาเตรียมข้อมูลสมาชิกที่มีค่า NULL กัน
เพื่อดูว่า `NULL` ทำงานอย่างไรในทางปฏิบัติ เรามาเตรียมข้อมูลตัวอย่างกันก่อน ครั้งนี้ เราจะสร้างตาราง members ง่ายๆ เพื่อจัดการข้อมูลสมาชิก ประเด็นสำคัญคือการจงใจใส่ระเบียนที่มีค่า `NULL` (สถานะที่ข้อมูลว่างเปล่า) เช่น หมายเลขโทรศัพท์และวันที่เข้าสู่ระบบล่าสุด
-- หากมีตาราง members อยู่แล้วให้ลบออก (เพื่อให้สามารถทดสอบซ้ำได้)
DROP TABLE IF EXISTS members;
-- สร้างตาราง members ใหม่
CREATE TABLE members (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone_number TEXT, -- อนุญาตให้มีค่า NULL
last_login DATE, -- อนุญาตให้มีค่า NULL
points INTEGER DEFAULT 0
);
-- ใส่ข้อมูลเริ่มต้น
INSERT INTO members (id, name, email, phone_number, last_login, points) VALUES
(1, 'ยามาดะ ทาโร่', 'yamada@example.com', '090-1111-2222', '2025-06-25', 150),
(2, 'ซูซูกิ ฮานาโกะ', 'suzuki@example.com', NULL, '2025-07-01', 50),
(3, 'ซาโต้ จิโร่', 'sato@example.com', '080-3333-4444', NULL, 200),
(4, 'อิโต ซากุระ', 'ito@example.com', NULL, NULL, 0),
(5, 'วาตานาเบะ เคนตะ', 'watanabe@example.com', '', '2025-06-28', 300); -- หมายเลขโทรศัพท์เป็นสตริงว่าง
ข้อมูลนี้รวมถึงผู้ใช้ที่มีหมายเลขโทรศัพท์เป็น `NULL` (ID 2, 4) และผู้ใช้ที่มีวันที่เข้าสู่ระบบล่าสุดเป็น `NULL` (ID 3, 4) นอกจากนี้ โปรดสังเกตว่าหมายเลขโทรศัพท์ของวาตานาเบะ (ID 5) ไม่ใช่ `NULL` แต่เป็น **สตริงว่าง** ('') ความแตกต่างนี้จะมีความสำคัญในภายหลัง
กับดักที่ใหญ่ที่สุด: ทำไมถึงค้นหาด้วย `= NULL` ไม่ได้?
คงไม่เกินเลยที่จะกล่าวว่าผู้เริ่มต้น SQL 100% ต้องเคยตกหลุมพรางนี้ นั่นคือการเขียนโค้ดว่า WHERE phone_number = NULL แม้จะดูเหมือนว่ามันควรจะทำงานได้ถูกต้อง แต่เมื่อรันคำสั่งนี้แล้ว จะไม่มีข้อมูลใดๆ ถูกส่งกลับมาเลย
-- 【ผิด!】คำสั่งนี้จะไม่ทำงานตามที่ตั้งใจไว้
SELECT * FROM members WHERE phone_number = NULL;
ทำไมถึงเป็นเช่นนั้น? นั่นเป็นเพราะ `NULL` แสดงถึงสถานะพิเศษของ "ไม่มีค่า" หรือ "ไม่ทราบค่า" เนื่องจาก `NULL` ไม่ใช่ค่า การเปรียบเทียบใดๆ กับมัน (แม้กระทั่งการเปรียบเทียบกับ `NULL` เอง) จะไม่ให้ผลลัพธ์เป็น `TRUE` หรือ `FALSE` แต่เป็นสถานะที่สามคือ `UNKNOWN` (ไม่ทราบ) `WHERE` clause จะดึงเฉพาะระเบียนที่เงื่อนไขเป็น `TRUE` เท่านั้น ดังนั้นระเบียนที่ผลลัพธ์เป็น `UNKNOWN` จะไม่ถูกเลือกเลย
ลองนึกภาพแบบสอบถามที่มีคำถามว่า "อาหารที่ชอบ" หากคุณต้องการค้นหาคนที "ไม่ได้กรอก" คุณไม่สามารถหาเจอได้ด้วยการค้นหาคนที่เขียนว่า "ไม่ได้กรอก" คุณจำเป็นต้องค้นหาสถานะ "การไม่ได้กรอก" เอง
【พื้นฐาน】วิธีที่ถูกต้องในการใช้ `IS NULL` และ `IS NOT NULL`
SQL ได้เตรียมตัวดำเนินการพิเศษไว้สำหรับตรวจสอบสถานะพิเศษของ `NULL` นี้ นั่นคือ `IS NULL` และ `IS NOT NULL`
`IS NULL`: การดึงข้อมูลระเบียนที่ค่าเป็น NULL
หากคุณต้องการค้นหาระเบียนที่ค่าของคอลัมน์ใดคอลัมน์หนึ่งเป็น `NULL` (ข้อมูลว่างเปล่า) ให้ใช้ `IS NULL` แทน `= NULL`
สถานการณ์: "ต้องการค้นหาสมาชิกที่ยังไม่ได้ลงทะเบียนหมายเลขโทรศัพท์ (`phone_number` เป็น `NULL`)"
SELECT * FROM members WHERE phone_number IS NULL;
ผลลัพธ์:
id | name | email | phone_number | last_login | points
---|----------------|-------------------|--------------|------------|-------
2 | ซูซูกิ ฮานาโกะ | suzuki@example.com| NULL | 2025-07-01 | 50
4 | อิโต ซากุระ | ito@example.com | NULL | NULL | 0
ครั้งนี้ ผู้ใช้ที่มี ID 2 และ 4 ถูกดึงออกมาอย่างถูกต้องตามที่ตั้งใจ โปรดสังเกตว่าวาตานาเบะ (ID 5) ไม่ได้รวมอยู่ในผลลัพธ์นี้เนื่องจากหมายเลขโทรศัพท์ของเขาเป็น "สตริงว่าง" ไม่ใช่ `NULL`
`IS NOT NULL`: การดึงข้อมูลระเบียนที่ค่าไม่เป็น NULL
ในทางกลับกัน หากคุณต้องการค้นหาระเบียนที่มีข้อมูลอยู่ (คือค่าไม่ว่างเปล่า) ให้ใช้ `IS NOT NULL`
สถานการณ์: "ต้องการค้นหาสมาชิกที่มีประวัติการเข้าสู่ระบบล่าสุด (`last_login` ไม่ใช่ `NULL`)"
SELECT * FROM members WHERE last_login IS NOT NULL;
ผลลัพธ์:
id | name | email | phone_number | last_login | points
---|----------------|----------------------|----------------|------------|-------
1 | ยามาดะ ทาโร่ | yamada@example.com | 090-1111-2222 | 2025-06-25 | 150
2 | ซูซูกิ ฮานาโกะ | suzuki@example.com | NULL | 2025-07-01 | 50
5 | วาตานาเบะ เคนตะ | watanabe@example.com | | 2025-06-28 | 300
อย่างที่คุณเห็น การใช้ `IS NOT NULL` ช่วยให้คุณสามารถเลือกเฉพาะระเบียนที่ไม่มีข้อมูลขาดหายได้อย่างมีประสิทธิภาพ
การประยุกต์ใช้: ทำความเข้าใจความแตกต่างระหว่าง `NULL`, "สตริงว่าง", และ "0"
สิ่งที่ผู้เริ่มต้นมักจะจัดการเหมือนกับ `NULL` คือ "สตริงว่าง ('')" และ "ตัวเลข 0" ซึ่งใน SQL นั้นถือว่าเป็นสิ่งที่แตกต่างกันอย่างชัดเจน
- `NULL`: สถานะ "ไม่ทราบ" หรือ "ไม่มีอยู่"
- สตริงว่าง (
''): "ค่า" ที่เป็นสตริงที่มีความยาวเป็นศูนย์ 0: "ค่า" ที่เป็นตัวเลขศูนย์
หมายเลขโทรศัพท์ของวาตานาเบะ (ID 5) เป็นสตริงว่าง ไม่ใช่ `NULL` ดังนั้น คำสั่งต่อไปนี้จะพบเฉพาะผู้ใช้ที่มี ID 5 เท่านั้น
SELECT * FROM members WHERE phone_number = '';
ในทำนองเดียวกัน คะแนนของอิโต (ID 4) คือ `0` แต่ก็แตกต่างจาก `NULL` เช่นกัน เมื่อใช้ฟังก์ชันการรวมข้อมูลอย่าง `COUNT` ความแตกต่างนี้จะยิ่งชัดเจนขึ้น
-- จำนวนสมาชิกทั้งหมด, จำนวนสมาชิกที่ลงทะเบียนหมายเลขโทรศัพท์, และผลรวมของคะแนน
SELECT COUNT(*), COUNT(phone_number), SUM(points) FROM members;
ผลลัพธ์:
COUNT(*) | COUNT(phone_number) | SUM(points)
--------|---------------------|------------
5 | 3 | 650
COUNT(*) จะนับทุกระเบียนจึงได้ 5 แต่ COUNT(phone_number) จะไม่นับค่า `NULL` จึงได้ 3 (ID 1, 3, 5) ในทำนองเดียวกัน SUM(points) ก็จะยกเว้นระเบียนที่มีค่า `NULL` ออกจากการคำนวณ พฤติกรรมนี้มีความสำคัญอย่างยิ่งในการวิเคราะห์ข้อมูล ดังนั้นควรจำไว้ให้ดี
【สนามเด็กเล่นแบบโต้ตอบ】มาตรวจสอบพฤติกรรมของ NULL ด้วยการรัน SQL ในเบราว์เซอร์กัน!
ถึงเวลาเปลี่ยนความรู้ให้เป็นทักษะแล้ว! คัดลอกโค้ด HTML ทั้งหมดด้านล่าง, บันทึกเป็นไฟล์ชื่อ sql_null_test.html, และเปิดในเบราว์เซอร์ของคุณ สภาพแวดล้อม SQL ส่วนตัวของคุณจะเปิดขึ้นมา พร้อมกับตาราง members ที่เราใช้ในบทความนี้
มาดูกันด้วยตาตัวเองว่าผลลัพธ์ระหว่าง = NULL กับ IS NULL แตกต่างกันอย่างไร และทำไม COUNT(*) กับ COUNT(ชื่อคอลัมน์) ถึงให้ผลลัพธ์ที่ต่างกัน มาลงมือทำกันเลย!
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>สนามเด็กเล่นออนไลน์สำหรับ SQL NULL</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.7; color: #333; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #34495e; }
textarea { width: 100%; height: 180px; font-family: "SF Mono", "Consolas", monospace; font-size: 16px; padding: 12px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #34495e; color: white; border: none; padding: 12px 22px; font-size: 16px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; }
button:hover { background-color: #2c3e50; }
button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
#result-container { margin-top: 2rem; border: 1px solid #ddd; padding: 1rem; border-radius: 6px; background: #fdfdfd; min-height: 50px; }
#error-message { color: #e74c3c; font-weight: bold; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
</head>
<body>
<h1>มาลองใช้ SQL กัน!</h1>
<p>ป้อนคำสั่ง SQL ในช่องข้อความด้านล่างแล้วคลิกปุ่ม "ประมวลผล" ลองใช้เงื่อนไขต่างๆ ดูสิ!</p>
<textarea id="sql-input">-- ค้นหาสมาชิกที่ไม่เคยเข้าสู่ระบบเลย (last_login เป็น NULL)
SELECT * FROM members WHERE last_login IS NULL;
</textarea>
<button id="execute-btn">ประมวลผล</button>
<div id="result-container">
<p id="error-message"></p>
<div id="result-output"></div>
</div>
<script>
const sqlInput = document.getElementById('sql-input');
const executeBtn = document.getElementById('execute-btn');
const errorMsg = document.getElementById('error-message');
const resultOutput = document.getElementById('result-output');
let db;
async function initDb() {
executeBtn.disabled = true;
executeBtn.textContent = 'กำลังเตรียมฐานข้อมูล...';
try {
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${file}`
});
db = new SQL.Database();
const setupSql = `
DROP TABLE IF EXISTS members;
CREATE TABLE members (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, phone_number TEXT, last_login DATE, points INTEGER DEFAULT 0);
INSERT INTO members (id, name, email, phone_number, last_login, points) VALUES
(1, 'ยามาดะ ทาโร่', 'yamada@example.com', '090-1111-2222', '2025-06-25', 150),
(2, 'ซูซูกิ ฮานาโกะ', 'suzuki@example.com', NULL, '2025-07-01', 50),
(3, 'ซาโต้ จิโร่', 'sato@example.com', '080-3333-4444', NULL, 200),
(4, 'อิโต ซากุระ', 'ito@example.com', NULL, NULL, 0),
(5, 'วาตานาเบะ เคนตะ', 'watanabe@example.com', '', '2025-06-28', 300);
`;
db.run(setupSql);
executeBtn.disabled = false;
executeBtn.textContent = 'ประมวลผล';
resultOutput.innerHTML = '<p>พร้อมแล้ว! คุณสามารถทดลองใช้คำสั่ง SQL ของคุณเองได้เลย</p>';
} catch (err) {
errorMsg.textContent = 'การเริ่มต้นฐานข้อมูลล้มเหลว: ' + err.message;
console.error(err);
}
}
function executeSql() {
if (!db) return;
const sql = sqlInput.value;
errorMsg.textContent = '';
resultOutput.innerHTML = '';
try {
const results = db.exec(sql);
if (results.length === 0) {
resultOutput.innerHTML = '<p>คำสั่งสำเร็จ แต่ไม่มีผลลัพธ์ส่งกลับมา</p>';
return;
}
results.forEach(result => {
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
result.columns.forEach(colName => {
const th = document.createElement('th');
th.textContent = colName;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
result.values.forEach(row => {
const bodyRow = document.createElement('tr');
row.forEach(cellValue => {
const td = document.createElement('td');
td.textContent = cellValue === null ? 'NULL' : cellValue;
bodyRow.appendChild(td);
});
tbody.appendChild(bodyRow);
});
table.appendChild(thead);
table.appendChild(tbody);
resultOutput.appendChild(table);
});
} catch (err) {
errorMsg.textContent = 'เกิดข้อผิดพลาด SQL: ' + err.message;
console.error(err);
}
}
executeBtn.addEventListener('click', executeSql);
initDb();
</script>
</body>
</html>
สรุป
ในบทความนี้ เราได้เรียนรู้เกี่ยวกับวิธีจัดการ `NULL` ใน SQL อย่างถูกต้อง ทั้งแนวคิดและวิธีการตรวจสอบที่เฉพาะเจาะจง
- ตัวตนของ `NULL`: `NULL` คือสถานะพิเศษที่หมายถึง "ไม่มีค่า" หรือ "ไม่ทราบ" ซึ่งแตกต่างจาก "0" หรือ "สตริงว่าง" โดยสิ้นเชิง
- กฎการเปรียบเทียบ: การเปรียบเทียบกับ `NULL` (
=,<>) จะให้ผลลัพธ์เป็น `UNKNOWN` เสมอ ทำให้ไม่ได้ผลลัพธ์ที่คาดหวังในWHEREclause - วิธีการตรวจสอบที่ถูกต้อง: วิธีเดียวที่ถูกต้องในการตรวจสอบว่าเป็น `NULL` หรือไม่ คือการใช้
IS NULLและIS NOT NULL - ความสัมพันธ์กับฟังก์ชันการรวมข้อมูล: ฟังก์ชันการรวมข้อมูลอย่าง
COUNT(ชื่อคอลัมน์),SUM(), และAVG()จะไม่นำระเบียนที่มีค่า `NULL` มารวมในการคำนวณโดยอัตโนมัติ
การทำความเข้าใจแนวคิดของ `NULL` อย่างถูกต้องเป็นเส้นทางที่หลีกเลี่ยงไม่ได้ในการเป็นผู้เชี่ยวชาญ SQL อาจจะสับสนเล็กน้อยในตอนแรก แต่ถ้าคุณจำไว้เสมอว่า "`NULL` ไม่ใช่ค่า แต่เป็นสถานะ" คุณจะค่อยๆคุ้นเคยกับมันเอง ทักษะนี้จำเป็นอย่างยิ่งสำหรับการจัดการข้อมูลที่ขาดหายไปอย่างถูกต้องและพัฒนาแอปพลิเคชันที่แข็งแกร่งและมีบั๊กน้อยลง ระดับ SQL ของคุณได้ก้าวขึ้นไปอีกขั้นแล้ว!