ทำความรู้จักกับ Self Join และทำไมต้องใช้
เวลาเราเก็บข้อมูลในฐานข้อมูล (Database) บางครั้งข้อมูลเหล่านั้นมีความสัมพันธ์กับตัวมันเอง เช่น ข้อมูลพนักงานที่ต้องระบุว่าใครเป็นหัวหน้าของใคร หากเราเก็บรายชื่อพนักงานและชื่อหัวหน้าไว้ในตารางเดียวกัน การจะดึงชื่อหัวหน้าออกมาแสดงผลจะทำได้ยากถ้าเราไม่รู้วิธีเชื่อมตารางเข้ากับตัวเอง หรือที่เรียกว่า Self Join (การเชื่อมตารางเข้ากับตัวเองเพื่อดึงข้อมูลที่สัมพันธ์กันออกมา)
ลองนึกภาพว่าคุณมีสมุดโทรศัพท์ที่มีชื่อคน และมีคอลัมน์บอกว่าคนนี้เป็นเพื่อนกับใครในสมุดเล่มเดียวกัน หากคุณต้องการทราบว่าเพื่อนของเพื่อนชื่ออะไร คุณก็ต้องเปิดสมุดเล่มเดิมซ้ำสองรอบเพื่อเทียบข้อมูล การทำ Self Join ก็คือการสั่งให้ฐานข้อมูลมองตารางตารางเดียวเป็นสองตารางแยกกัน เพื่อให้เราเปรียบเทียบข้อมูลภายในตารางนั้นได้สะดวกขึ้นนั่นเอง
การเข้าใจเรื่องนี้สำคัญมากสำหรับโปรแกรมเมอร์ เพราะระบบส่วนใหญ่ในโลกจริงมักมีโครงสร้างแบบลำดับชั้น (Hierarchy) เช่น ผังองค์กร ระบบสายงานอ้างอิง หรือแม้แต่ระบบหมวดหมู่สินค้าที่ซ้อนกันหลายชั้น ถ้าคุณดึงข้อมูลเหล่านี้ไม่เป็น คุณจะเสียเวลาเขียนโปรแกรมวนลูป (Loop - การทำงานซ้ำๆ) เพื่อดึงข้อมูลทีละบรรทัด ซึ่งทำให้โปรแกรมทำงานช้าและกินทรัพยากรเครื่องอย่างมาก
วิธีการเขียนคำสั่ง Self Join แบบพื้นฐาน
หัวใจสำคัญของการทำ Self Join คือการใช้ AS (การตั้งชื่อเล่นให้ตาราง) เพื่อแยกตารางเดิมออกเป็นสองชื่อสมมติ เพื่อให้เราสามารถอ้างอิงข้อมูลจากตารางเดียวกันได้โดยไม่สับสน เหมือนการเรียกชื่อเล่นให้เพื่อนสองคนที่มีชื่อจริงเหมือนกันเพื่อให้คุยกันรู้เรื่องว่าเราหมายถึงใคร
สมมติเรามีตาราง employees (ตารางเก็บข้อมูลพนักงาน) ที่มีคอลัมน์ id, name และ manager_id (รหัสหัวหน้างาน) หากเราต้องการดึงรายชื่อพนักงานคู่กับชื่อหัวหน้า เราต้องเชื่อมตาราง employees เข้ากับตัวมันเองโดยเทียบค่า manager_id ของพนักงานกับ id ของหัวหน้างาน
จุดที่มือใหม่มักพลาดคือการลืมตั้งชื่อเล่นให้ตาราง ทำให้ฐานข้อมูลไม่รู้ว่าเรากำลังอ้างอิงคอลัมน์จากส่วนไหนของตาราง ถ้าไม่ตั้งชื่อให้ชัดเจน ฐานข้อมูลจะฟ้อง Error (ข้อผิดพลาด) ทันที เพราะมันหาคอลัมน์ที่ซ้ำชื่อกันไม่เจอว่าควรดึงตัวไหนมาใช้งาน
-- เชื่อมตาราง employees สองชุดด้วยชื่อ e1 และ e2
SELECT e1.name AS employee_name, e2.name AS manager_name
FROM employees AS e1
LEFT JOIN employees AS e2 ON e1.manager_id = e2.id;
คำอธิบายโค้ด: e1 คือตารางพนักงานที่เราโฟกัส ส่วน e2 คือตารางเดียวกันที่ใช้หาชื่อหัวหน้า LEFT JOIN คือการสั่งให้ดึงพนักงานทุกคนออกมา แม้บางคนจะไม่มีหัวหน้าก็ตาม ส่วน ON คือเงื่อนไขการเชื่อม โดยเอารหัสหัวหน้าของพนักงานไปเทียบกับรหัสประจำตัวของหัวหน้า
ผลลัพธ์ที่ควรเห็น: ตารางจะแสดงชื่อพนักงานในคอลัมน์แรก และชื่อหัวหน้าของเขาในคอลัมน์ที่สอง ถ้าพนักงานคนไหนไม่มีหัวหน้า (เช่น CEO) คอลัมน์หัวหน้างานจะเป็นค่า NULL (ค่าว่างหรือไม่มีข้อมูล)
รวมผลลัพธ์จากหลายที่ด้วย UNION และ UNION ALL
บางครั้งเราต้องการนำข้อมูลจากหลายตารางหรือหลายเงื่อนไขมารวมเป็นก้อนเดียวกันเพื่อแสดงผล UNION (คำสั่งรวมผลลัพธ์จากหลาย Query) คือเครื่องมือที่ใช้รวมผลลัพธ์จากสองคำสั่งเข้าด้วยกัน โดยมีกฎว่าจำนวนคอลัมน์และประเภทข้อมูลต้องตรงกัน
ลองจินตนาการว่าคุณมีรายชื่อลูกค้าจากสองสาขาที่เก็บข้อมูลแยกตารางกัน หากต้องการทำรายการรายชื่อลูกค้าทั้งหมดเพื่อส่งอีเมลหาทุกคน คุณก็แค่เอาผลลัพธ์จากสาขาแรกมาวางต่อด้วยสาขาที่สอง นี่คือหน้าที่หลักของ UNION ซึ่งนอกจากจะรวมข้อมูลแล้ว มันยังช่วยตัดข้อมูลที่ซ้ำกันออกไปให้โดยอัตโนมัติด้วย
แต่ถ้าคุณต้องการเก็บข้อมูลทั้งหมดไว้โดยไม่สนว่าจะซ้ำหรือไม่ ต้องใช้ UNION ALL แทน มือใหม่มักใช้ UNION โดยไม่จำเป็น ซึ่งทำให้ฐานข้อมูลต้องทำงานหนักขึ้นเพื่อตรวจสอบข้อมูลซ้ำ ถ้าคุณมั่นใจว่าข้อมูลไม่มีทางซ้ำกัน หรือต้องการแสดงผลทั้งหมดแม้จะซ้ำ ให้เลือกใช้ UNION ALL จะทำงานได้เร็วกว่ามาก
-- รวมชื่อจากตารางพนักงานและรายชื่อผู้รับเหมา
SELECT name FROM employees
UNION
SELECT name FROM contractors;
คำอธิบายโค้ด: คำสั่งแรกดึงชื่อจากตารางพนักงาน คำสั่งที่สองดึงชื่อจากตารางผู้รับเหมา UNION จะรวมผลลัพธ์เข้าด้วยกันและลบชื่อที่ซ้ำกันออกไป หากต้องการให้แสดงทั้งหมดไม่ว่าจะซ้ำหรือไม่ ให้เปลี่ยนไปใช้ UNION ALL แทน
ผลลัพธ์ที่ควรเห็น: รายชื่อทุกคนจากทั้งสองตารางจะเรียงต่อกันเป็นคอลัมน์เดียว หากชื่อไหนมีอยู่ทั้งในตารางพนักงานและผู้รับเหมา ชื่อนั้นจะถูกแสดงเพียงครั้งเดียวในผลลัพธ์สุดท้าย
ข้อควรระวังในการใช้งาน UNION
การใช้ UNION มีข้อจำกัดที่ต้องระวังคือจำนวนคอลัมน์ในแต่ละคำสั่งต้องเท่ากันเป๊ะ หากคำสั่งแรกดึงมา 3 คอลัมน์ คำสั่งที่สองก็ต้องดึงมา 3 คอลัมน์เช่นกัน ถ้าคอลัมน์ไม่เท่ากัน ฐานข้อมูลจะปฏิเสธการทำงาน และแจ้งข้อผิดพลาดออกมาทันที
นอกจากนี้ ลำดับของคอลัมน์ก็สำคัญมาก ข้อมูลในคอลัมน์แรกของคำสั่งแรกจะถูกนำไปรวมกับข้อมูลในคอลัมน์แรกของคำสั่งที่สอง หากคุณดึงชื่อพนักงานไว้คอลัมน์แรก แต่คำสั่งที่สองดึงรหัสพนักงานไว้คอลัมน์แรก ข้อมูลของคุณจะปนกันมั่วจนใช้งานไม่ได้
อีกจุดหนึ่งที่มือใหม่มักลืมคือการเรียงลำดับผลลัพธ์ หากต้องการใช้ ORDER BY (คำสั่งเรียงลำดับข้อมูล) ให้ใส่ไว้ที่บรรทัดสุดท้ายของคำสั่งรวมเท่านั้น ฐานข้อมูลจะนำผลลัพธ์ทั้งหมดมารวมกันก่อน แล้วค่อยทำการเรียงลำดับตามที่เรากำหนดไว้ทีเดียว
-- รวมข้อมูลและเรียงลำดับผลลัพธ์
SELECT name, 'Employee' AS type FROM employees
UNION ALL
SELECT name, 'Contractor' AS type FROM contractors
ORDER BY name;
คำอธิบายโค้ด: เราเพิ่มคอลัมน์ type เข้าไปเพื่อบอกว่าชื่อนี้มาจากตารางไหน UNION ALL จะรวมข้อมูลทั้งหมดเข้าด้วยกันโดยไม่ตัดข้อมูลซ้ำ และ ORDER BY name จะทำการเรียงชื่อทั้งหมดตามตัวอักษรหลังจากรวมเสร็จแล้ว
ผลลัพธ์ที่ควรเห็น: ตารางแสดงสองคอลัมน์ คือชื่อและประเภทสถานะ โดยข้อมูลทั้งหมดจะถูกเรียงลำดับชื่อจาก ก ถึง ฮ หรือ A ถึง Z อย่างเป็นระเบียบ
เทคนิคการฝึกฝนเพื่อก้าวสู่โปรแกรมเมอร์มืออาชีพ
การเรียนรู้เรื่อง Self Join หรือ UNION ไม่ใช่แค่การท่องจำคำสั่ง แต่คือการฝึกคิดเชิงตรรกะว่าเราจะดึงข้อมูลที่ซับซ้อนออกมาให้ง่ายได้อย่างไร ลองหาโจทย์จากเว็บไซต์ฝึกเขียน SQL (ภาษาสำหรับจัดการฐานข้อมูล) แล้วลองแก้ปัญหาด้วยวิธีเหล่านี้ดู
สำหรับคนที่กำลังเตรียมตัวสัมภาษณ์งาน ให้ฝึกเขียนเคสจำลอง เช่น การสร้างผังองค์กร หรือการทำระบบสมาชิกที่แนะนำคนอื่นต่อกัน (Referral System) การใช้ Self Join ในสถานการณ์จริงจะช่วยให้คุณอธิบายได้ฉะฉานตอนสัมภาษณ์ว่าคุณเข้าใจโครงสร้างข้อมูลระดับลึก ไม่ใช่แค่การดึงข้อมูลจากตารางเดียว
อย่าลืมทดลองเขียนโค้ดด้วยตัวเองทุกครั้ง อย่าแค่ดูผ่านตา การเจอบั๊ก (Bug - ข้อผิดพลาดในโปรแกรม) จากการพิมพ์ผิดหรือลืมตั้งชื่อตาราง คือบทเรียนที่ดีที่สุด การแก้บั๊กด้วยตัวเองจะทำให้คุณจดจำวิธีเขียนที่ถูกต้องได้แม่นยำกว่าการอ่านตำราเป็นสิบเล่ม
สรุป: การนำไปใช้จริงในโปรเจกต์ของคุณ
สรุปสั้นๆ คือ Self Join มีไว้เพื่อจัดการข้อมูลที่มีความสัมพันธ์กันเองในตารางเดียว ส่วน UNION มีไว้รวมผลลัพธ์จากหลายที่เข้าด้วยกัน การเข้าใจเครื่องมือเหล่านี้จะทำให้คุณจัดการฐานข้อมูลในโปรเจกต์จริงได้ยืดหยุ่นขึ้นมาก โดยเฉพาะเมื่อต้องสร้างระบบที่ซับซ้อน เช่น ระบบจัดการสมาชิก
ตัวอย่างการนำไปใช้จริง: สมมติคุณกำลังทำเว็บไซต์ขายของออนไลน์ คุณอาจมีตาราง categories (หมวดหมู่สินค้า) ที่มีคอลัมน์ parent_id เพื่อบอกว่าหมวดหมู่นี้เป็นลูกของหมวดหมู่ไหน คุณสามารถใช้ Self Join เพื่อดึงชื่อหมวดหมู่หลักและหมวดหมู่ย่อยออกมาแสดงในหน้าเว็บพร้อมกันได้โดยไม่ต้องยิงคำสั่งถามฐานข้อมูลหลายรอบ
ขอให้มองว่าฐานข้อมูลเป็นเหมือนจิ๊กซอว์ การรู้จักคำสั่งเหล่านี้คือการรู้วิธีต่อชิ้นส่วนที่ซับซ้อนให้เข้าที่ ฝึกฝนบ่อยๆ แล้วคุณจะพบว่าการเขียนโค้ดจัดการข้อมูลเป็นเรื่องสนุกและท้าทายความสามารถของคุณในทุกๆ วันที่ได้ลองทำโปรเจกต์ใหม่ๆ