Trigger คืออะไรและทำไมต้องรู้จัก
เวลาเราทำงานกับฐานข้อมูล บางครั้งเราต้องการให้ระบบทำงานบางอย่างโดยอัตโนมัติเมื่อเกิดเหตุการณ์บางอย่างขึ้น เช่น เมื่อมีการลบข้อมูลพนักงานออก เราอยากให้ระบบย้ายข้อมูลนั้นไปเก็บในตารางสำรองทันทีโดยที่เราไม่ต้องสั่งเอง Trigger (ตัวกระตุ้นการทำงานอัตโนมัติ) คือเครื่องมือที่ช่วยจัดการเรื่องนี้ได้ดีที่สุดใน MS-SQL
ลองนึกภาพว่าคุณมีพนักงานคอยเฝ้าหน้าประตูร้าน ถ้าลูกค้าเดินเข้าประตูมาพนักงานคนนี้จะกล่าวต้อนรับทันทีโดยไม่ต้องรอให้คุณสั่ง นี่คือหลักการทำงานของ Trigger ในฐานข้อมูล มันจะคอยเฝ้าดูคำสั่ง Query (คำสั่งถามหรือจัดการข้อมูล) อย่างเช่นการเพิ่ม การแก้ไข หรือการลบข้อมูลในตารางที่คุณกำหนดไว้
การใช้ Trigger ช่วยให้ข้อมูลในฐานข้อมูลของเรามีความถูกต้องและเป็นระบบอยู่เสมอ โดยเฉพาะเมื่อเรามีตารางที่ต้องสัมพันธ์กันหลายจุด การเขียนโค้ดไว้ที่ตัวฐานข้อมูลโดยตรงจะช่วยป้องกันความผิดพลาดจากการลืมสั่งงานผ่านแอปพลิเคชันได้เป็นอย่างดี ถือเป็นทักษะสำคัญที่โปรแกรมเมอร์มืออาชีพต้องมีติดตัวไว้
รู้จักกับ AFTER Trigger และการนำไปใช้
AFTER Trigger (ตัวกระตุ้นหลังการทำงาน) เป็นประเภทที่นิยมใช้กันมากที่สุดตามชื่อของมันเลยครับ คือระบบจะทำงานหลังจากคำสั่ง Insert (เพิ่มข้อมูล), Update (แก้ไขข้อมูล) หรือ Delete (ลบข้อมูล) สำเร็จลงในตารางเรียบร้อยแล้ว เหมาะสำหรับงานประเภทการเก็บประวัติหรือการตรวจสอบข้อมูลย้อนหลัง
ตัวอย่างเช่น คุณต้องการเก็บประวัติว่าใครเข้ามาแก้ไขเงินเดือนพนักงานตอนไหนบ้าง คุณสามารถสร้าง Trigger ที่คอยดักจับเหตุการณ์ Update แล้วบันทึกค่าเก่าและค่าใหม่ลงในตารางประวัติทันที ตารางเสมือน (ตารางชั่วคราวที่เก็บข้อมูลก่อนและหลังเปลี่ยนแปลง) ที่ชื่อว่า inserted และ deleted จะถูกสร้างขึ้นมาให้เราใช้งานภายใน Trigger นี้โดยเฉพาะ
ข้อควรระวังคืออย่าใส่คำสั่งที่ซับซ้อนเกินไปใน Trigger เพราะทุกครั้งที่คุณสั่งงานตารางหลัก Trigger จะทำงานตามไปด้วย ถ้ามันทำงานช้าจะทำให้แอปพลิเคชันของคุณหน่วงตามไปด้วยเสมอ ให้เขียนเฉพาะงานที่จำเป็นจริงๆ เพื่อรักษาความเร็วของระบบ
-- สร้าง Trigger สำหรับบันทึกประวัติการแก้ไขข้อมูล
CREATE TRIGGER trg_LogSalaryChange
ON Employees
AFTER UPDATE
AS
BEGIN
INSERT INTO SalaryHistory (EmployeeID, OldSalary, NewSalary, ChangeDate)
SELECT d.EmployeeID, d.Salary, i.Salary, GETDATE()
FROM deleted d
JOIN inserted i ON d.EmployeeID = i.EmployeeID
END
คำอธิบายโค้ด: บรรทัดที่ 3 สร้าง Trigger ชื่อ trg_LogSalaryChange ผูกไว้กับตาราง Employees บรรทัดที่ 4 ระบุว่าให้ทำงานหลังเหตุการณ์ UPDATE บรรทัดที่ 7-10 คือการดึงข้อมูลจากตาราง deleted (ค่าก่อนแก้) และ inserted (ค่าหลังแก้) มาเก็บไว้ในตารางประวัติ
ผลลัพธ์ที่ควรเห็น: เมื่อคุณสั่ง UPDATE Employees SET Salary = 50000 WHERE ID = 1 ข้อมูลเงินเดือนเดิมและใหม่จะถูกเพิ่มเข้าไปในตาราง SalaryHistory โดยอัตโนมัติทันที
การทำงานของ INSTEAD OF Trigger
INSTEAD OF Trigger (ตัวกระตุ้นแทนที่การทำงาน) ทำงานต่างออกไป มันจะเข้าไปขัดขวางคำสั่งเดิมที่กำลังจะเกิดขึ้นแล้วทำสิ่งที่เขียนไว้แทนที่ ปกติเราใช้เมื่อต้องการดัดแปลงข้อมูลก่อนบันทึกจริง หรือใช้กับ View (ตารางจำลองที่สร้างจากคำสั่ง Query) ที่ไม่สามารถแก้ไขข้อมูลได้โดยตรง
ลองจินตนาการว่าคุณมีกฎว่าห้ามลบข้อมูลลูกค้าทิ้งจริงๆ แต่ให้เปลี่ยนสถานะเป็น "เลิกใช้งาน" แทน ในกรณีนี้ INSTEAD OF Trigger จะเข้ามาสวมรอยแทนคำสั่ง Delete ของผู้ใช้ แล้วเปลี่ยนเป็นการสั่ง Update สถานะแทน ทำให้ข้อมูลจริงในตารางยังคงอยู่ครบถ้วนตามความต้องการของธุรกิจ
การใช้ Trigger แบบนี้ต้องระวังเรื่องตรรกะให้ดี เพราะถ้าเขียนพลาดอาจทำให้คำสั่งบันทึกข้อมูลไม่ทำงานเลย หรือทำงานผิดพลาดไปจากที่ควรจะเป็น แนะนำให้ทดสอบในสภาพแวดล้อมจำลองจนมั่นใจก่อนนำไปใช้กับฐานข้อมูลจริงของลูกค้า
-- สร้าง Trigger แทนที่คำสั่งลบข้อมูล
CREATE TRIGGER trg_SoftDeleteCustomer
ON Customers
INSTEAD OF DELETE
AS
BEGIN
UPDATE Customers
SET IsActive = 0
WHERE CustomerID IN (SELECT CustomerID FROM deleted)
END
คำอธิบายโค้ด: บรรทัดที่ 4 ระบุว่าให้ทำงาน INSTEAD OF DELETE คือแทนที่การลบ บรรทัดที่ 7-9 คือการเปลี่ยนสถานะ IsActive ของลูกค้าเป็น 0 แทนการลบแถวข้อมูลนั้นออกไปจริงๆ
ผลลัพธ์ที่ควรเห็น: เมื่อรันคำสั่ง DELETE FROM Customers WHERE CustomerID = 10 ข้อมูลแถวนั้นจะไม่หายไป แต่ค่าในคอลัมน์ IsActive จะเปลี่ยนจาก 1 เป็น 0 แทน
จัดการข้อผิดพลาดด้วย TRY...CATCH
ในการเขียนโปรแกรม Error Handling (การจัดการข้อผิดพลาด) เป็นสิ่งสำคัญมาก เพราะโปรแกรมที่ไม่มีการดักจับข้อผิดพลาดจะทำให้ระบบล่มหรือข้อมูลพังได้ง่ายๆ TRY...CATCH คือโครงสร้างมาตรฐานใน MS-SQL ที่ใช้ครอบโค้ดที่มีความเสี่ยงจะเกิดปัญหา
หลักการทำงานคือ เราจะเอาโค้ดที่อาจเกิดปัญหาไปวางไว้ในบล็อก BEGIN TRY หากโค้ดส่วนนั้นทำงานผิดพลาด ระบบจะกระโดดไปทำงานในบล็อก BEGIN CATCH ทันที ทำให้เราสามารถจัดการกับปัญหาที่เกิดขึ้นได้ เช่น การบันทึก Log หรือการแจ้งเตือนผู้ดูแลระบบ
การเขียนโปรแกรมที่ดีต้องคิดถึงกรณีที่เลวร้ายที่สุดเสมอ เช่น ถ้าฐานข้อมูลเต็ม หรือมีการส่งข้อมูลผิดประเภทเข้ามา ถ้าเราไม่ดักจับไว้ ระบบจะหยุดทำงานทันทีและสร้างความเสียหายต่อประสบการณ์ของผู้ใช้งาน
BEGIN TRY
-- พยายามลบข้อมูลที่อาจไม่มีอยู่จริง
DELETE FROM Orders WHERE OrderID = 999999
END TRY
BEGIN CATCH
PRINT 'เกิดข้อผิดพลาดในการลบข้อมูล'
PRINT ERROR_MESSAGE() -- แสดงข้อความแจ้งเตือนจากระบบ
END CATCH
คำอธิบายโค้ด: บรรทัดที่ 1-4 คือส่วนที่พยายามทำงาน บรรทัดที่ 5-8 คือส่วนที่ทำงานเมื่อเกิดข้อผิดพลาด ERROR_MESSAGE() เป็นฟังก์ชันที่บอกรายละเอียดว่าทำไมถึงรันไม่ผ่าน
ผลลัพธ์ที่ควรเห็น: หน้าจอแสดงผลข้อความ 'เกิดข้อผิดพลาดในการลบข้อมูล' และรายละเอียดของข้อผิดพลาดที่เกิดขึ้นจริง แทนที่จะเป็นหน้าจอแจ้งเตือน Error แบบดิบๆ ของระบบ
ยกระดับการจัดการด้วยคำสั่ง THROW
เมื่อเราเจอข้อผิดพลาดแล้ว การแค่พิมพ์ข้อความออกมาอาจไม่เพียงพอ บางครั้งเราต้องส่งสัญญาณบอกแอปพลิเคชันหลักว่าเกิดอะไรขึ้น THROW (คำสั่งโยนข้อผิดพลาด) ช่วยให้เราสามารถสร้าง Error ของเราเองขึ้นมาได้ เพื่อให้ระบบตรวจจับได้ชัดเจนว่านี่คือปัญหาที่ผู้เขียนโปรแกรมตั้งใจดักไว้
การใช้ THROW คู่กับ TRY...CATCH จะทำให้ระบบเราดูเป็นมืออาชีพมากขึ้น คุณสามารถกำหนดรหัสข้อผิดพลาดและข้อความที่สื่อสารกับทีมพัฒนาคนอื่นๆ ได้ชัดเจน เช่น ถ้าข้อมูลที่กรอกมาไม่ตรงตามเงื่อนไขธุรกิจ ให้ THROW ข้อความแจ้งเตือนกลับไปทันที
จำไว้ว่าการเขียนโค้ดไม่ใช่แค่ทำให้มันทำงานได้ แต่ต้องทำให้มันบอกได้ว่า "ทำไมมันถึงทำงานไม่ได้" เมื่อเกิดปัญหาขึ้นจริงๆ การมีระบบแจ้งเตือนที่ชัดเจนจะช่วยให้คุณประหยัดเวลาในการซ่อมแซมโค้ดได้มหาศาล
BEGIN TRY
IF NOT EXISTS (SELECT * FROM Products WHERE Stock > 0)
THROW 50001, 'สินค้าหมดสต็อก ไม่สามารถทำรายการได้', 1
END TRY
BEGIN CATCH
PRINT 'พบปัญหา: ' + ERROR_MESSAGE()
END CATCH
คำอธิบายโค้ด: บรรทัดที่ 3 คือการตรวจสอบเงื่อนไขธุรกิจ บรรทัดที่ 4 คือการสั่ง THROW พร้อมรหัส 50001 และข้อความแจ้งเตือน ERROR_MESSAGE() ในบล็อก CATCH จะจับข้อความนี้มาแสดงผล
ผลลัพธ์ที่ควรเห็น: ระบบจะพิมพ์ข้อความ 'พบปัญหา: สินค้าหมดสต็อก ไม่สามารถทำรายการได้' ออกมา ทำให้รู้ทันทีว่าติดปัญหาที่จุดไหน
สรุป: การนำไปใช้จริงในงานพัฒนาซอฟต์แวร์
การเขียนโปรแกรมที่ดีต้องคำนึงถึงความปลอดภัยของข้อมูลเสมอ ทั้งการใช้ Trigger เพื่อตรวจสอบความถูกต้อง และการใช้ TRY...CATCH เพื่อป้องกันระบบล่ม การฝึกฝนเรื่องเหล่านี้ตั้งแต่เนิ่นๆ จะทำให้คุณเป็นโปรแกรมเมอร์ที่เขียนโค้ดได้อย่างรัดกุมและน่าเชื่อถือมากขึ้น
สมมติว่าคุณกำลังทำระบบร้านค้าออนไลน์ คุณควรใช้ Trigger ในการอัปเดตจำนวนสินค้าคงเหลือทันทีที่มีการสั่งซื้อ และใช้ THROW เพื่อแจ้งเตือนหากลูกค้ากดสั่งซื้อในขณะที่สินค้าหมด การทำแบบนี้จะช่วยให้ระบบของคุณจัดการข้อมูลได้แม่นยำแม้จะมีผู้ใช้งานจำนวนมากพร้อมกัน
เริ่มฝึกจากการสร้าง Trigger ง่ายๆ ในฐานข้อมูลส่วนตัวของคุณดูก่อน เมื่อคล่องแล้วค่อยลองเพิ่มระบบจัดการ Error เข้าไป การเป็นโปรแกรมเมอร์ที่เก่งไม่ได้วัดกันที่เขียนโค้ดเร็วแค่ไหน แต่วัดที่ว่าโค้ดของคุณพังยากแค่ไหนและแก้ไขได้รวดเร็วเพียงใดเมื่อมีปัญหาเกิดขึ้นครับ