PostgreSQL Triggers คืออะไรและทำไมต้องรู้จัก
เวลาเราเขียนโปรแกรมจัดการฐานข้อมูล (Database) หลายครั้งเราอยากให้ระบบทำงานบางอย่างให้อัตโนมัติ เช่น พอมีคนสั่งซื้อสินค้า เราอยากให้ระบบตัดสต็อกสินค้าทันที PostgreSQL Triggers (ตัวสั่งการอัตโนมัติ) คือเครื่องมือที่ช่วยให้ฐานข้อมูล "เฝ้าระวัง" เหตุการณ์ต่างๆ ที่เกิดขึ้นกับข้อมูลของเรา
ลองนึกภาพว่าคุณมีพนักงานคอยจดบันทึกทุกครั้งที่มีคนเดินเข้าหรือออกจากร้าน Trigger ก็เปรียบเสมือนพนักงานคนนั้นที่คอยเฝ้าดู Event (เหตุการณ์ที่เกิดขึ้น) เช่น การเพิ่มข้อมูล การแก้ไข หรือการลบข้อมูล เมื่อเหตุการณ์นั้นเกิดขึ้น มันจะสั่งให้ฟังก์ชันที่เราเตรียมไว้ทำงานทันทีโดยที่เราไม่ต้องเขียนโค้ดสั่งซ้ำในโปรแกรมหลัก
ทำไมโปรแกรมเมอร์ต้องรู้เรื่องนี้ เพราะมันช่วยลดความซ้ำซ้อนของโค้ดในฝั่งแอปพลิเคชันได้มาก เราไม่ต้องกังวลว่าลืมเขียนคำสั่งตัดสต็อกในบางหน้าจอ เพราะฐานข้อมูลจะจัดการให้เองเสมอไม่ว่าคำสั่งจะมาจากทางไหน การใช้ Trigger จะช่วยให้ข้อมูลในระบบของเรามีความถูกต้องและสอดคล้องกันอยู่ตลอดเวลา
โครงสร้างของ Trigger และฟังก์ชันที่ต้องเตรียม
การสร้าง Trigger ใน PostgreSQL จะประกอบด้วยสองส่วนหลักเสมอ ส่วนแรกคือ Trigger Function (ฟังก์ชันที่จะให้ทำงานเมื่อเกิดเหตุการณ์) และส่วนที่สองคือตัว Trigger เองที่จะกำหนดว่าให้ฟังก์ชันนี้ทำงานตอนไหนและกับตารางไหนบ้าง
ฟังก์ชันที่ใช้กับ Trigger จะมีความพิเศษคือมันต้องไม่มีการรับค่า (Parameter) และต้องคืนค่าเป็นชนิด Trigger เสมอ ภายในฟังก์ชันนี้เราสามารถเข้าถึงข้อมูลพิเศษอย่าง NEW (ข้อมูลชุดใหม่ที่กำลังจะเพิ่มหรือแก้) หรือ OLD (ข้อมูลชุดเดิมก่อนที่จะถูกแก้ไขหรือลบ) ได้อย่างสะดวก
มือใหม่มักสับสนว่าทำไมต้องแยกเป็นสองส่วน คำตอบคือเพื่อความยืดหยุ่นครับ เราสามารถสร้างฟังก์ชันไว้หนึ่งชุด แล้วเอาไปผูกกับหลายตารางหรือหลายเหตุการณ์ได้ ทำให้เราไม่ต้องเขียนโค้ดซ้ำซ้อน และยังจัดการแก้ไขงานได้ง่ายในจุดเดียวเมื่อต้องการปรับปรุงเงื่อนไขการทำงานในอนาคต
-- สร้างฟังก์ชันที่จะให้ทำงานอัตโนมัติ
CREATE OR REPLACE FUNCTION log_last_update()
RETURNS TRIGGER AS $$
BEGIN
-- อัปเดตเวลาปัจจุบันลงในคอลัมน์ updated_at
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
บรรทัดที่ 1 คือการสร้างฟังก์ชัน บรรทัดที่ 2 บอกว่าฟังก์ชันนี้เป็นแบบพิเศษสำหรับ Trigger บรรทัดที่ 4 คือการสั่งให้ข้อมูลใหม่ที่กำลังจะบันทึกเปลี่ยนค่าเวลาเป็นเวลาปัจจุบัน บรรทัดที่ 5 คือการส่งข้อมูลที่แก้ไขแล้วกลับไปบันทึกจริง บรรทัดที่ 7 คือการระบุว่าใช้ภาษา PL/pgSQL (ภาษาสำหรับเขียนคำสั่งในฐานข้อมูล) ในการเขียน
ผลลัพธ์ที่ควรเห็นคือ เราจะได้ฟังก์ชันชื่อ log_last_update เก็บไว้ในฐานข้อมูล พร้อมให้เรานำไปผูกกับตารางต่างๆ ได้ทันที
ความแตกต่างของ BEFORE และ AFTER
เราต้องเลือกว่าจะให้ Trigger ทำงานตอนไหนระหว่าง BEFORE (ก่อนที่ข้อมูลจะถูกบันทึกจริง) หรือ AFTER (หลังจากที่ข้อมูลบันทึกสำเร็จแล้ว) การเลือกใช้ให้ถูกสถานการณ์เป็นทักษะสำคัญที่โปรแกรมเมอร์มืออาชีพต้องมี เพื่อความปลอดภัยของข้อมูล
ถ้าเราใช้ BEFORE เราสามารถแก้ไขข้อมูลที่กำลังจะบันทึกได้ เช่น การตรวจสอบว่าราคาสินค้าติดลบหรือไม่ หรือการแปลงตัวอักษรให้เป็นตัวพิมพ์ใหญ่ทั้งหมดก่อนเก็บลงตาราง แต่ถ้าใช้ AFTER เราจะแก้ไขข้อมูลที่กำลังบันทึกไม่ได้แล้ว เพราะมันถูกบันทึกลงไปเรียบร้อยแล้ว เหมาะสำหรับงานที่ต้องทำตามหลัง เช่น การส่งอีเมลแจ้งเตือน หรือการบันทึกประวัติการแก้ไขลงตารางอื่น
ข้อควรระวังคือ ถ้าเราใช้ BEFORE แล้วเกิดข้อผิดพลาดในฟังก์ชัน ข้อมูลรายการนั้นจะไม่ถูกบันทึกลงฐานข้อมูลเลย แต่ถ้าใช้ AFTER ข้อมูลจะถูกบันทึกไปแล้ว ถึงแม้ฟังก์ชันจะพังก็ตาม ดังนั้นถ้าเป็นงานสำคัญที่เกี่ยวกับความถูกต้องของข้อมูล ให้เลือกใช้ BEFORE เป็นอันดับแรกเสมอ
การติดตั้ง Trigger เข้ากับตาราง
เมื่อเรามีฟังก์ชันแล้ว ขั้นตอนต่อไปคือการสร้าง Trigger เพื่อเชื่อมฟังก์ชันนั้นเข้ากับตารางที่ต้องการ เราต้องระบุให้ชัดเจนว่าจะให้มันทำงานตอนไหน (INSERT, UPDATE, DELETE) และจะให้ทำงานกับทุกแถวที่เปลี่ยน หรือทำงานครั้งเดียวต่อหนึ่งคำสั่ง
การตั้งชื่อ Trigger ให้สื่อความหมายเป็นเรื่องที่ดีมากครับ เช่น trigger_update_timestamp เพื่อให้เรากลับมาอ่านโค้ดแล้วรู้ทันทีว่ามันทำหน้าที่อะไร การทำแบบนี้จะช่วยให้โปรเจกต์ขนาดใหญ่ของเราจัดการได้ง่ายขึ้นเมื่อต้องทำงานร่วมกับคนอื่นในทีม
จุดที่มือใหม่มักพลาดคือการลืมระบุคำว่า FOR EACH ROW ซึ่งคำนี้สำคัญมาก เพราะมันบอกว่าให้ Trigger ทำงานกับทุกแถวที่ได้รับผลกระทบ หากไม่ใส่ Trigger อาจจะไม่ทำงานในรูปแบบที่เราคาดหวังไว้ หรือทำงานผิดพลาดในกรณีที่มีการแก้ไขข้อมูลหลายแถวพร้อมกัน
-- ผูกฟังก์ชันเข้ากับตาราง users
CREATE TRIGGER set_timestamp_trigger
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION log_last_update();
บรรทัดที่ 1 คือการสั่งสร้าง Trigger ชื่อ set_timestamp_trigger บรรทัดที่ 2 ระบุว่าให้ทำก่อนการอัปเดตตาราง users บรรทัดที่ 3 คือการสั่งให้ทำงานทุกแถวที่โดนแก้ไข บรรทัดที่ 4 คือการเรียกใช้ฟังก์ชันที่เราสร้างไว้ในหัวข้อก่อนหน้า
ผลลัพธ์ที่ควรเห็นคือ เมื่อมีการอัปเดตข้อมูลในตาราง users คอลัมน์ updated_at จะถูกเปลี่ยนเป็นเวลาปัจจุบันโดยอัตโนมัติทุกครั้ง
ข้อดีและข้อเสียที่ต้องพิจารณา
ข้อดีที่เห็นได้ชัดของ Trigger คือความ เป็นอิสระ ของฐานข้อมูล ไม่ว่าเราจะใช้ภาษาเขียนโปรแกรมอะไรมาเชื่อมต่อกับฐานข้อมูลนี้ (เช่น Python, JavaScript, PHP) กฎที่เราสร้างไว้ใน Trigger ก็จะทำงานเสมอ ทำให้เรามั่นใจได้ว่าข้อมูลจะถูกต้องตามเงื่อนไขที่วางไว้
แต่เหรียญมีสองด้านเสมอครับ ข้อเสียคือ Trigger มักจะซ่อนตัวอยู่ภายในฐานข้อมูล ทำให้โปรแกรมเมอร์คนอื่นที่เข้ามาดูโค้ดแอปพลิเคชันอาจจะงงว่า "ข้อมูลเปลี่ยนไปได้อย่างไร" เพราะในโค้ดฝั่งแอปพลิเคชันไม่มีคำสั่งนั้นอยู่ หากใช้มากเกินไปจะทำให้ระบบซับซ้อนจนหาต้นตอของปัญหาได้ยาก
คำแนะนำสำหรับมือใหม่คือ ให้ใช้ Trigger ในงานที่จำเป็นจริงๆ เช่น การทำ Audit Log (ตารางบันทึกประวัติการแก้ไขข้อมูล) หรือการตรวจสอบความถูกต้องของข้อมูลระดับพื้นฐาน อย่าใช้ Trigger เพื่อทำธุรกิจหลัก (Business Logic) ที่ซับซ้อนเกินไป เพราะจะทำให้การดูแลรักษาโค้ดในระยะยาวทำได้ยากขึ้น
สรุป: การประยุกต์ใช้ในงานจริง
เราได้เรียนรู้แล้วว่า Trigger คือตัวช่วยให้ฐานข้อมูลทำงานอัตโนมัติ ซึ่งเหมาะมากสำหรับงานที่ต้องการความชัวร์ เช่น การบันทึกเวลาแก้ไขล่าสุด หรือการคัดลอกข้อมูลไปเก็บในตารางสำรองเมื่อมีการลบข้อมูลออก เพื่อป้องกันการข้อมูลหายโดยไม่ได้ตั้งใจ
ลองนึกถึงสถานการณ์ที่คุณทำระบบสมาชิก หากคุณต้องการเก็บบันทึกว่า "ใครลบรายชื่อสมาชิกทิ้งไป" คุณสามารถใช้ Trigger แบบ AFTER DELETE เพื่อดึงข้อมูลที่ถูกลบไปเก็บไว้ในตาราง deleted_users_log ก่อนที่ข้อมูลนั้นจะหายไปจากระบบอย่างถาวร วิธีนี้ปลอดภัยและเป็นมาตรฐานที่บริษัทซอฟต์แวร์นิยมใช้กัน
จงฝึกใช้ Trigger ในโปรเจกต์เล็กๆ ของตัวเองก่อน แล้วคุณจะพบว่ามันช่วยลดภาระการเขียนโค้ดได้มหาศาล ขอให้สนุกกับการปรับแต่งฐานข้อมูลให้ฉลาดขึ้น และอย่าลืมจดบันทึกทุกครั้งที่คุณสร้าง Trigger เพื่อให้ทีมงานคนอื่นเข้าใจระบบของคุณได้ง่ายขึ้นด้วยครับ