أهلاً يا صديقي المبرمج!
اليوم راح نتكلم عن وحدة من أقوى وأحياناً أخطر أدوات الـ SQL: القوادح أو Triggers. ببساطة، الـ Trigger هو كود SQL يتنفذ تلقائياً لما يصير حدث معين على جدولك. تخيل إنك حارس شخصي لقاعدة بياناتك، الـ Trigger هو العين اللي ما تنام!
إيش هي الـ Triggers بالضبط؟
الـ Trigger هو إجراء مخزن (Stored Procedure) خاص جداً، يرتبط بجدول معين ويتنفذ بشكل آلي لما يصير حدث من أحداث معالجة البيانات (DML Events) عليه. الأحداث هذي هي: INSERT, UPDATE, أو DELETE. يعني لو أحد أضاف صف جديد، أو عدّل صف موجود، أو حذف صف، الـ Trigger ممكن يشتغل.
ليش ممكن نحتاج الـ Triggers؟
فيه أسباب كثيرة تخليك تفكر تستخدمها:
- التحقق من البيانات (Data Validation): تتأكد إن البيانات المدخلة صحيحة قبل ما تنحفظ.
- التدقيق والتسجيل (Auditing & Logging): تسجل مين سوى إيش ومتى، وهذا مفيد جداً للمتابعة الأمنية أو تتبع التغييرات.
- الحفاظ على تكامل البيانات (Maintaining Data Integrity): تتأكد إن العلاقات بين الجداول صحيحة، وتحدث الجداول المرتبطة تلقائياً.
- إنشاء قيم تلقائية (Generating Derived Columns): تحسب قيمة عمود بناءً على قيم أعمدة أخرى في نفس الصف أو في صفوف أخرى.
- فرض قواعد أعمال معقدة (Enforcing Complex Business Rules): أحياناً تكون قواعد العمل معقدة لدرجة ما تقدر تطبقها بقيود (Constraints) عادية.
أنواع الـ Triggers (من حيث التوقيت والأحداث)
فيه نوعين رئيسيين من حيث التوقيت:
BEFORETriggers: تتنفذ قبل حدوث الحدث (INSERT, UPDATE, DELETE). هذي مفيدة جداً لو تبغى تعدل البيانات اللي جاية قبل ما تنحفظ، أو تمنع عملية معينة إذا كانت البيانات غير صالحة.AFTERTriggers: تتنفذ بعد حدوث الحدث. هذي ممتازة لو تبغى تسجل الحدث بعد ما يتم، أو تحدث جداول ثانية بناءً على التغيير اللي صار.
أما من حيث الأحداث، فالـ Trigger يرتبط بواحد أو أكثر من هذي الأحداث:
INSERT: لما يتم إضافة صف جديد.UPDATE: لما يتم تعديل صف موجود.DELETE: لما يتم حذف صف.
بنية الـ Trigger الأساسية
الشكل العام لإنشاء Trigger يكون كذا (ملاحظة: البنية تختلف شوي بين قواعد البيانات زي MySQL, PostgreSQL, SQL Server, Oracle لكن المفهوم واحد):
هنا راح نستخدم بنية قريبة من MySQL أو PostgreSQL اللي هي شائعة جداً في الويب.
CREATE TRIGGER trigger_name
[BEFORE | AFTER] [INSERT | UPDATE | DELETE]
ON table_name
FOR EACH ROW
BEGIN
-- كود الـ SQL اللي تبغاه يتنفذ هنا
-- تقدر تستخدم NEW للإشارة للبيانات الجديدة (في INSERT و UPDATE)
-- وتقدر تستخدم OLD للإشارة للبيانات القديمة (في UPDATE و DELETE)
END;
trigger_name: اسم مميز للـ Trigger حقك.BEFORE | AFTER: تحدد متى يتنفذ (قبل أو بعد الحدث).INSERT | UPDATE | DELETE: تحدد على أي حدث يتنفذ. ممكن تحط أكثر من واحد بـORفي بعض الأنظمة أو تسوي Trigger لكل حدث.ON table_name: تحدد على أي جدول يرتبط الـ Trigger.FOR EACH ROW: هذا يعني إن الـ Trigger راح يتنفذ لكل صف يتأثر بالعملية. هذا هو الأكثر شيوعاً. فيهFOR EACH STATEMENTبس مو كل قواعد البيانات تدعمه وعادةً ما نحتاجه بالقدر اللي نحتاجFOR EACH ROW.BEGIN ... END;: داخلها تكتب كود الـ SQL اللي تبغاه يتنفذ.NEW: متغير خاص يحمل قيم الأعمدة للصف الجديد (فيINSERT) أو قيم الأعمدة بعد التعديل (فيUPDATE).OLD: متغير خاص يحمل قيم الأعمدة للصف قبل التعديل (فيUPDATE) أو قيم الأعمدة للصف المحذوف (فيDELETE).
أمثلة عملية على الـ Triggers
مثال 1: التحقق من البيانات قبل الإضافة (BEFORE INSERT)
نفترض عندنا جدول للمنتجات ونبغى نتأكد إن سعر المنتج ما يكون سالب، وإذا ما تحدد تاريخ الإضافة نحط التاريخ الحالي تلقائياً.
-- أول شي نسوي الجدول
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- نسوي الـ Trigger
DELIMITER //
CREATE TRIGGER check_product_price_before_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Product price cannot be negative!';
END IF;
-- لو ما تم تحديد created_at بشكل صريح، نضع التاريخ الحالي
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
END //
DELIMITER ;
-- نجرب نضيف منتج بسعر سالب (راح يفشل)
INSERT INTO products (product_name, price) VALUES ('Test Product 1', -10.00);
-- نجرب نضيف منتج بسعر صحيح (راح ينجح)
INSERT INTO products (product_name, price) VALUES ('Laptop', 1200.00);
-- نجرب نضيف منتج بسعر صحيح وتاريخ إضافة محدد
INSERT INTO products (product_name, price, created_at) VALUES ('Mouse', 25.00, '2023-01-15 10:00:00');
-- نشوف المنتجات
SELECT * FROM products;
لاحظ استخدام
DELIMITER // ... DELIMITER ;في MySQL عشان تسمح لنا نستخدم الفاصلة المنقوطة داخل الـ Trigger بدون ما تنهي أمرCREATE TRIGGERنفسه. في PostgreSQL أو SQL Server ما تحتاجها عادةً.
SIGNAL SQLSTATE '45000'هو طريقة لرفع خطأ ومنع العملية من الاستمرار.
مثال 2: تسجيل التغييرات بعد التعديل (AFTER UPDATE)
نبغى نسجل كل مرة يتغير فيها سعر منتج معين، مين اللي غيره ومتى والسعر القديم والجديد.
-- أول شي نسوي جدول للتدقيق
CREATE TABLE product_price_audit (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT,
old_price DECIMAL(10, 2),
new_price DECIMAL(10, 2),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
changed_by VARCHAR(255) DEFAULT USER() -- (في MySQL) أو GETUTCDATE() في SQL Server
);
-- نسوي الـ Trigger
DELIMITER //
CREATE TRIGGER log_product_price_changes
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
IF OLD.price <> NEW.price THEN -- نتأكد إن السعر فعلاً تغير
INSERT INTO product_price_audit (product_id, old_price, new_price)
VALUES (OLD.product_id, OLD.price, NEW.price);
END IF;
END //
DELIMITER ;
-- نجرب نعدل سعر منتج
UPDATE products SET price = 1250.00 WHERE product_id = 2; -- Laptop
-- نجرب نعدل اسم منتج فقط (ما راح يسجل بالـ audit)
UPDATE products SET product_name = 'Gaming Laptop' WHERE product_id = 2;
-- نشوف جدول التدقيق
SELECT * FROM product_price_audit;
هنا استخدمنا
OLD.priceعشان نوصل للسعر القديم قبل التعديل، وNEW.priceللسعر الجديد بعد التعديل.الدالة
USER()ترجع اسم المستخدم الحالي لقاعدة البيانات في MySQL، تقدر تستخدم شي مشابه في قواعد البيانات الأخرى.
مثال 3: تحديث مخزون المنتجات بعد إضافة طلب (AFTER INSERT)
تخيل عندك جدول للمنتجات وجدول لطلبات العملاء. لما العميل يسوي طلب، نبغى ننقص كمية المنتج من المخزون تلقائياً.
-- جدول المنتجات (مع عمود للكمية)
-- (لو ما سويته من قبل، بس لو سويته في الأمثلة اللي فوق ضيف عمود quantity)
ALTER TABLE products ADD COLUMN quantity INT DEFAULT 0;
UPDATE products SET quantity = 100 WHERE product_id = 2; -- Laptop
-- جدول الطلبات
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT,
quantity_ordered INT,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- نسوي الـ Trigger
DELIMITER //
CREATE TRIGGER decrease_product_quantity_after_order
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE products
SET quantity = quantity - NEW.quantity_ordered
WHERE product_id = NEW.product_id;
END //
DELIMITER ;
-- نجرب نسوي طلب جديد
INSERT INTO orders (product_id, quantity_ordered) VALUES (2, 5); -- طلب 5 لابتوبات
-- نشوف كمية اللابتوب في جدول المنتجات بعد الطلب
SELECT product_name, quantity FROM products WHERE product_id = 2;
-- نجرب نسوي طلب ثاني
INSERT INTO orders (product_id, quantity_ordered) VALUES (2, 3);
-- نشوف الكمية مرة ثانية
SELECT product_name, quantity FROM products WHERE product_id = 2;
هذا مثال ممتاز على كيف الـ Triggers ممكن تحافظ على تكامل البيانات بين الجداول وتطبق قواعد العمل تلقائياً.
حذف الـ Trigger
إذا ما عاد تحتاج الـ Trigger، تقدر تحذفه بسهولة:
DROP TRIGGER trigger_name;
مثلاً، لحذف الـ Trigger الأول:
DROP TRIGGER check_product_price_before_insert;
نقاط مهمة لازم تحطها في بالك!
- الأداء (Performance): الـ Triggers ممكن تأثر على أداء قاعدة البيانات إذا كانت معقدة جداً أو تتنفذ على عمليات كثيرة. كل عملية
INSERT,UPDATE,DELETEراح تشغل الـ Trigger. - صعوبة التصحيح (Debugging Difficulty): تتبع الأخطاء في الـ Triggers ممكن يكون صعب، لأنها تتنفذ في الخلفية.
- تسلسل الـ Triggers (Trigger Chaining): Trigger ممكن يشغل Trigger ثاني، اللي بدوره يشغل ثالث، وهكذا. هذا ممكن يؤدي لمشاكل غير متوقعة أو حلقات لا نهائية إذا ما انتبهت.
- البدائل (Alternatives): أحياناً يكون من الأفضل إنك تنفذ نفس المنطق في كود التطبيق حقك بدلاً من الـ Trigger، خصوصاً إذا كان المنطق معقد جداً أو يحتاج تفاعل مع المستخدم.
- التعقيد الخفي (Hidden Complexity): الـ Triggers تضيف طبقة من المنطق اللي مو ظاهرة مباشرة في كود التطبيق، وهذا ممكن يصعب على المطورين الجدد فهم النظام.
خلاصة الكلام
الـ Triggers أداة قوية جداً في يد المبرمج المحترف. استخدمها بحكمة وفي الأماكن اللي فعلاً تحتاجها عشان تفرض قواعد عمل مهمة أو تحافظ على تكامل البيانات. لا تبالغ في استخدامها عشان لا تحول قاعدة بياناتك لمتاهة!
أتمنى يكون الدرس واضح ومفيد. بالتوفيق في رحلتك مع الـ SQL!