SQL Interview Prep · บทเรียน

NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT

วิธีที่ NULL มีพฤติกรรมแตกต่างกันในการจัดกลุ่ม การเชื่อมตาราง และการตรวจค่าที่ไม่ซ้ำกัน

บทเรียน 4 จาก 413 ขั้นตอน

NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT เป็นบทเรียน SQL Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

NULL ในสามบริบทที่น่าประหลาดใจ

NULL ไม่ได้ทำงานเหมือนกันในทุกบริบท บทเรียนสุดท้ายนี้ครอบคลุมสามบริบทที่ทำให้ผู้สมัครประหลาดใจมากที่สุด: ฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT / GROUP BY

ประเด็นที่เกิดซ้ำคือ ฟังก์ชันรวมและการกรองมอง NULL ว่า “ให้ข้ามไป” แต่การจัดกลุ่มและ DISTINCT มอง NULL ว่า “เป็นค่าหนึ่งที่เท่ากับ NULL อื่น” ความไม่สอดคล้องนี้คือสิ่งที่ผู้สัมภาษณ์มักทดสอบ

เมื่อเข้าใจเรื่องเหล่านี้อย่างเชี่ยวชาญ คุณก็จะครอบคลุมคำถามเกี่ยวกับ NULL ที่พบบ่อยที่สุดในการสัมภาษณ์ SQL

ฟังก์ชันรวมไม่สนใจ NULL

กฎสำคัญคือ ฟังก์ชันรวมจะข้าม NULL SUM, AVG, MIN, MAX และ COUNT ของคอลัมน์จะไม่สนใจค่าป้อนเข้าที่เป็น NULL โดยสิ้นเชิง แทนที่จะถือว่าค่าเหล่านั้นเป็นศูนย์

นี่คือเหตุผลที่ AVG อาจคืนค่าต่างจากที่คุณคาดไว้ เพราะจะหารผลรวมของค่าที่ไม่ใช่ NULL ด้วย จำนวนค่าที่ไม่ใช่ NULL ไม่ใช่ด้วยจำนวนแถวทั้งหมด

-- bonus values: 100, 200, NULL
SELECT
  SUM(bonus) AS total,   -- 300 (NULL ignored)
  AVG(bonus) AS average, -- 150 = 300 / 2, not / 3
  COUNT(bonus) AS cnt    -- 2 (NULL not counted)
FROM employees;

COUNT(*) เทียบกับ COUNT(column)

นี่คือคำถามเกี่ยวกับ NULL ในฟังก์ชันรวมที่ถูกถามบ่อยที่สุด COUNT(*) จะนับ แถว รวมถึงแถวที่มี NULL ส่วน COUNT(column) จะนับเฉพาะแถวที่คอลัมน์นั้นมีค่า ไม่ใช่ NULL

ดังนั้นความแตกต่างระหว่างทั้งสองก็คือจำนวน NULL ในคอลัมน์นั้นพอดี ส่วน COUNT(DISTINCT column) จะไปไกลกว่านั้น โดยไม่นับ NULL และตัดค่าซ้ำออกด้วย

SELECT
  COUNT(*)              AS rows_total,    -- all rows
  COUNT(bonus)          AS non_null_bonus, -- excludes NULLs
  COUNT(DISTINCT bonus) AS distinct_bonus, -- excludes NULLs + dups
  COUNT(*) - COUNT(bonus) AS null_bonus
FROM employees;

AVG เทียบกับ SUM/COUNT(*): กับดักคลาสสิก

ผู้สัมภาษณ์อาจถามว่า “AVG(x) เหมือนกับ SUM(x) / COUNT(*) หรือไม่” คำตอบคือ ไม่เหมือน เมื่อมี NULL อยู่

AVG(x) เท่ากับ SUM(x) / COUNT(x) โดยหารด้วยจำนวนค่าที่ไม่ใช่ NULL หากใช้ COUNT(*) แทน จะเท่ากับถือว่า NULL เป็นศูนย์ ทำให้ค่าเฉลี่ยลดลง

หากคุณต้องการนับ NULL เป็นศูนย์จริง ๆ ต้องระบุอย่างชัดเจนด้วย COALESCE

-- These differ when bonus has NULLs:
SELECT
  AVG(bonus)                       AS avg_ignoring_nulls,
  SUM(bonus) * 1.0 / COUNT(*)      AS avg_nulls_as_zero,
  AVG(COALESCE(bonus, 0))          AS explicit_nulls_as_zero
FROM employees;

กรณีขอบของฟังก์ชันรวมที่อินพุตเป็น NULL ทั้งหมด

ฟังก์ชันรวมจะคืนค่าอะไรเมื่อค่าป้อนเข้าทั้งหมดเป็น NULL หรือไม่มีแถวเลย ผู้สัมภาษณ์ชอบให้แยกความแตกต่างที่ชัดเจนดังนี้:

  • SUM, AVG, MIN, MAX ที่ทำงานกับแถวที่เป็น NULL ทั้งหมดหรือไม่มีแถว จะคืนค่าเป็น NULL
  • COUNT จะคืนค่าเป็น 0 เสมอ ไม่ใช่ NULL

ดังนั้นหากรายงานแสดงยอดรวมว่างเปล่า สาเหตุที่เป็นไปได้คือ SUM ที่มีค่า NULL ทั้งหมด ให้ครอบด้วย COALESCE เพื่อแสดงค่า 0

-- No matching rows or all bonuses NULL:
SELECT SUM(bonus) FROM employees WHERE 1 = 0;  -- NULL
SELECT COUNT(bonus) FROM employees WHERE 1 = 0; -- 0

-- Present a clean zero:
SELECT COALESCE(SUM(bonus), 0) FROM employees;

NULL ในเงื่อนไขการเชื่อมตาราง

ในส่วน ON ของการเชื่อมตาราง NULL = NULL ยังคงให้ผลเป็น UNKNOWN ดังนั้น คีย์ที่เป็น NULL จะไม่จับคู่กัน ในการเชื่อมตารางด้วยเงื่อนไขความเท่ากัน แถวสองแถวที่มีคีย์สำหรับเชื่อมตารางเป็น NULL ทั้งคู่จะไม่ถูกจับคู่

กรณีนี้มักเกิดขึ้นเมื่อเชื่อมตารางด้วยคีย์นอกที่อาจไม่มีค่า หากต้องการให้ NULL จับคู่กับ NULL จริง คุณต้องใช้ตัวดำเนินการที่ปลอดภัยกับ NULL (IS NOT DISTINCT FROM หรือ <=>) จากบทเรียนก่อนหน้า

-- Rows with region IS NULL on both sides do NOT match
SELECT *
FROM a JOIN b ON a.region = b.region;

-- To match NULL-to-NULL (ANSI):
SELECT *
FROM a JOIN b ON a.region IS NOT DISTINCT FROM b.region;

NULL ที่เกิดจากการเชื่อมตารางแบบภายนอก

การเชื่อมตารางแบบภายนอกจะ สร้าง NULL ให้กับแถวที่ไม่พบคู่ หลังใช้ LEFT JOIN คอลัมน์ทุกคอลัมน์ทางขวาจะเป็น NULL สำหรับแถวทางซ้ายที่ไม่พบคู่

นี่คือพื้นฐานของ รูปแบบการค้นหาแถวที่ไม่มีคู่: กรองด้วย WHERE right_table.key IS NULL เพื่อค้นหาแถวที่ไม่มีคู่ เช่น ลูกค้าที่ไม่มีคำสั่งซื้อ

อย่างไรก็ตาม ควรระวังว่าการกรองคอลัมน์จากการเชื่อมตารางแบบภายนอกใน WHERE อาจเปลี่ยนให้กลับเป็นการเชื่อมตารางภายในโดยไม่ตั้งใจ ซึ่งเป็นหัวข้อของฉากถัดไป

-- Find customers who have never ordered (anti-join)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

กับดัก NULL จากการใช้ WHERE กับการเชื่อมตารางแบบภายนอก

นี่เป็นข้อผิดพลาดที่ผู้สัมภาษณ์ชอบนำมาถาม คุณใช้ LEFT JOIN กับตารางคำสั่งซื้อ แล้วเพิ่ม WHERE o.status = 'shipped' ทันใดนั้นลูกค้าที่ไม่มีคำสั่งซื้อก็หายไป ทำให้การเชื่อมตารางแบบภายนอกมีผลเทียบเท่ากับการเชื่อมตารางภายใน

เหตุผลคือ สำหรับแถวที่ไม่พบคู่ o.status จะเป็น NULL และ NULL = 'shipped' จะให้ผลเป็น UNKNOWN ดังนั้น WHERE จึงตัดแถวเหล่านั้นออก หากต้องการเก็บแถวที่ไม่พบคู่ไว้ ให้ย้ายเงื่อนไขไปไว้ในส่วน ON แทน

-- Accidental inner join: drops customers with no orders
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped';

-- Correct: keep unmatched customers
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id AND o.status = 'shipped';

DISTINCT ถือว่า NULL ทั้งหมดเท่ากัน

นี่คือความไม่สอดคล้องที่ทำให้ทุกคนประหลาดใจ ฟังก์ชันรวมจะข้าม NULL แต่ DISTINCT จะเก็บ NULL ไว้เพียงหนึ่งค่า โดยถือว่า NULL ทั้งหมดเป็นค่าซ้ำกัน

ดังนั้น SELECT DISTINCT bonus เมื่อใช้กับค่า 100, 100, NULL, NULL จะคืนมาสามแถว ได้แก่ 100, NULL และเพียงเท่านี้ NULL สองค่าจะถูกรวมเหลือค่าเดียว แม้ว่า NULL = NULL จะได้ UNKNOWN ในบริบทอื่น

-- bonus: 100, 100, NULL, NULL, 200
SELECT DISTINCT bonus FROM employees;
-- Returns: 100, 200, NULL  (the two NULLs become one row)

GROUP BY รวม NULL เป็นกลุ่มเดียว

GROUP BY ใช้กฎเดียวกับ DISTINCT: คีย์ NULL ทั้งหมดจะถูกรวบรวมเป็น กลุ่มเดียว ซึ่งตรงข้ามกับตรรกะการเปรียบเทียบที่ NULL จะไม่เท่ากันเลย

ดังนั้น การจัดกลุ่มตามคอลัมน์ที่ยอมให้เป็น NULL จะให้หนึ่งแถวแทนระเบียนทั้งหมดที่มีคีย์เป็น NULL ซึ่งโดยปกติเป็นสิ่งที่ต้องการสำหรับรายงาน โปรดกล่าวถึงความแตกต่างนี้ระหว่างการจัดกลุ่มกับการเปรียบเทียบเพื่อแสดงความเข้าใจเชิงลึก

-- All employees with NULL department form ONE group
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department;
-- A single row where department is NULL totals all of them

ประเด็นสำคัญสำหรับการสัมภาษณ์

บทสรุปที่เชื่อมโยงทุกประเด็นและสร้างความประทับใจให้ผู้สัมภาษณ์คือ:

  • ฟังก์ชันรวมจะไม่สนใจ NULL โดย AVG จะหารด้วยผลของ COUNT(คอลัมน์) ไม่ใช่ COUNT(*)
  • COUNT(*) นับแถว ส่วน COUNT(คอลัมน์) และ COUNT(DISTINCT คอลัมน์) จะข้าม NULL
  • SUM/AVG/MIN/MAX เมื่อไม่มีแถวจะคืนค่า NULL ส่วน COUNT จะคืนค่า 0
  • ในการ JOIN คีย์ที่เป็น NULL จะไม่ตรงกันเลย และการกรองคอลัมน์จากการ JOIN ภายนอกด้วย WHERE จะทำให้กลายเป็นการ JOIN ภายในโดยไม่แจ้งให้ทราบ
  • DISTINCT และ GROUP BY ถือว่า NULL ทั้งหมดเท่ากัน ซึ่งตรงข้ามกับตรรกะการเปรียบเทียบ

สรุปเป็นประโยคเดียวได้ว่า: 'NULL จะถูกละเว้นเมื่อรวมค่าและเปรียบเทียบ แต่จะถูกรวมไว้ด้วยกันเมื่อกำจัดค่าซ้ำ'

ตรวจสอบอย่างรวดเร็ว

ทดสอบความแตกต่างระหว่างการจัดกลุ่มกับการรวมค่า

ทบทวน

คุณได้เรียนรู้การจัดการ NULL สำหรับการสัมภาษณ์ครบถ้วนแล้ว:

  • ฟังก์ชันรวมจะ ข้าม NULL โดย AVG จะหารด้วยจำนวนค่าที่ไม่ใช่ NULL และ SUM ที่มีแต่ NULL จะได้ NULL ขณะที่ COUNT จะได้ 0
  • COUNT(*) นับแถวที่เป็น NULL ด้วย ส่วน COUNT(คอลัมน์) ไม่นับ และผลต่างระหว่างสองค่านี้เท่ากับจำนวน NULL
  • คีย์สำหรับ JOIN ที่เป็น NULL จะ ไม่ตรงกันเลย และการกรองคอลัมน์จากการ JOIN ภายนอกใน WHERE อาจทำให้กลายเป็นการ JOIN ภายใน
  • DISTINCT และ GROUP BY รวม NULL ทั้งหมดเป็นกลุ่มเดียว ซึ่งตรงข้ามกับตรรกะการเปรียบเทียบ

จงจำหลักสำคัญนี้ไว้: NULL จะถูกละเว้นเมื่อรวมค่าและเปรียบเทียบ แต่จะถูกรวมไว้ด้วยกันเมื่อกำจัดค่าซ้ำ ความเข้าใจเพียงประการเดียวนี้ตอบคำถามสัมภาษณ์เกี่ยวกับ NULL ได้เกือบทั้งหมด

เริ่มต้นได้ฟรี

เรียนรู้ SQL ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
30
บทเรียน
120

คำถามที่พบบ่อย

บทเรียน “NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT”

วิธีที่ NULL มีพฤติกรรมแตกต่างกันในการจัดกลุ่ม การเชื่อมตาราง และการตรวจค่าที่ไม่ซ้ำกัน คุณปฏิบัติ SQL Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน SQL Interview Prep นี้ได้ไหม

ได้ บทเรียน SQL Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ตรรกะสามค่าและ UNKNOWN
  2. IS NULL, IS NOT NULL และความเท่าเทียบที่ปลอดภัยต่อ NULL
  3. COALESCE, NULLIF และ ISNULL
  4. NULL ในฟังก์ชันรวม การเชื่อมตาราง และ DISTINCT
← กลับไปที่ SQL Interview Prep