المُحفِّزات والأحداث
المُحفِّزات والأحداث
تشبيه من الحياة الواقعية
تخيّل نظام منزل ذكي:
- المُحفِّز مثل "عندما يُشخص أحد جرس الباب، تشغّل ضوء المدخل تلقائياً" — عندما يحدث حدث معين، يُنفَّذ إجراء مُحدَّد مسبقاً تلقائياً.
- مُجدول الأحداث مثل "كل يوم الساعة 7 صباحاً، افتح الستائر تلقائياً" — المهام تُنفَّذ دورياً وفقاً لجدول زمني.
المفاهيم الأساسية
ما هو المُحفِّز
المُحفِّز هو كائن قاعدة بيانات مرتبط بجدول يتم تنفيذه تلقائياً عند حدوث حدث معين على ذلك الجدول:
- يُفعَّل قبل أو بعد إدراج (INSERT) البيانات، أو تحديثها (UPDATE)، أو حذفها (DELETE)
- يمكنه الوصول إلى البيانات المعدّلة (مراجع NEW و OLD)
- يُستخدم لتطبيق قواعد أعمال معقدة، وسجلات التدقيق، وقيود سلامة البيانات
توقيت المُحفِّز: BEFORE مقابل AFTER
| التوقيت | الوصف | الاستخدام النموذجي |
|---|---|---|
| BEFORE | يُفعَّل قبل تنفيذ العملية | التحقق من صحة البيانات، التعديل التلقائي للقيم |
| AFTER | يُفعَّل بعد تنفيذ العملية | سجلات التدقيق، التحديثات المتتالية |
أنواع أحداث المُحفِّز
- INSERT: يُفعَّل عند إدراج سجل جديد
- UPDATE: يُفعَّل عند تحديث سجل
- DELETE: يُفعَّل عند حذف سجل
مراجع NEW و OLD
يمكنك الوصول إلى البيانات التي تتم معالجتها داخل المُحفِّز:
| الحدث | NEW | OLD |
|---|---|---|
| INSERT | الصف المُدرج حديثاً | غير متاح |
| UPDATE | القيمة بعد التحديث | القيمة قبل التحديث |
| DELETE | غير متاح | الصف المحذوف |
-- مثال SQLite: الوصول إلى NEW و OLD
-- NEW.column_name: يشير إلى البيانات الجديدة
-- OLD.column_name: يشير إلى البيانات القديمة
حالات استخدام المُحفِّزات النموذجية
- سجلات التدقيق: تسجيل من عدّل أي بيانات ومتى
- التحديثات المتتالية: تحديث البيانات في الجداول ذات الصلة تلقائياً
- التحقق من صحة البيانات: إجراء التحقق من قواعد الأعمال المعقدة قبل كتابة البيانات
- الحساب التلقائي: حساب قيم الحقول المشتقة تلقائياً
- الاحتفاظ بالبيانات المكررة: تحديث جداول الملخص أو ذاكرة التخزين المؤقت بشكل متزامن
معرفة موسعة: مُجدول الأحداث في قواعد البيانات الأخرى
يتيح مُجدول الأحداث تنفيذ المهام تلقائياً وفقاً لجدول زمني:
-- مثال مُجدول الأحداث في MySQL
-- تفعيل مُجدول الأحداث
SET GLOBAL event_scheduler = ON;
-- إنشاء حدث مجدول: تنظيف البيانات المنتهية الصلاحية يومياً عند منتصف الليل
CREATE EVENT cleanup_expired_orders
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 00:00:00'
DO
DELETE FROM orders
WHERE status = 'expired'
AND created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
-- إنشاء حدث لمرة واحدة
CREATE EVENT one_time_report
ON SCHEDULE AT '2024-12-31 23:59:59'
DO
INSERT INTO annual_report (year, total_sales)
SELECT YEAR(NOW()), SUM(amount) FROM orders WHERE YEAR(created_at) = YEAR(NOW());
-- عرض جميع الأحداث
SHOW EVENTS;
-- تعطيل حدث
ALTER EVENT cleanup_expired_orders DISABLE;
-- حذف حدث
DROP EVENT IF EXISTS cleanup_expired_orders;
الأساسيات
صيغة المُحفِّز في SQLite
SQLite يدعم المُحفِّزات بالصيغة التالية:
-- صيغة إنشاء المُحفِّز الأساسية
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
-- منطق المُحفِّز
END;
صيغة المُحفِّز في MySQL
-- صيغة المُحفِّز في MySQL
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
-- منطق المُحفِّز
-- استخدم NEW و OLD للإشارة إلى البيانات
END //
DELIMITER ;
صيغة المُحفِّز في PostgreSQL
-- PostgreSQL يتطلب إنشاء دالة مُحفِّز أولاً
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
-- منطق المُحفِّز
RETURN NEW; -- أو RETURN OLD
END;
$$ LANGUAGE plpgsql;
-- ثم إنشاء المُحفِّز
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
EXECUTE FUNCTION trigger_function_name();
أمثلة
مثال: إنشاء مُحفِّز سجل تدقيق (الصعوبة ⭐⭐⭐)
إنشاء مُحفِّز يسجل تلقائياً تاريخ تعديل رواتب الموظفين.
-- نسخة SQLite
-- أولاً إنشاء جدول سجل التدقيق
CREATE TABLE IF NOT EXISTS salary_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id INTEGER,
old_salary REAL,
new_salary REAL,
changed_by TEXT,
changed_at TEXT DEFAULT (datetime('now', 'localtime')),
action TEXT
);
-- إنشاء المُحفِّز: تسجيل تحديثات الراتب
CREATE TRIGGER audit_salary_update
AFTER UPDATE ON employees
FOR EACH ROW
WHEN OLD.salary != NEW.salary
BEGIN
INSERT INTO salary_audit_log (
employee_id,
old_salary,
new_salary,
changed_by,
action
)
VALUES (
NEW.id,
OLD.salary,
NEW.salary,
'system', -- عملياً، يمكنك استخدام المستخدم الحالي
'UPDATE'
);
END;
-- اختبار المُحفِّز
UPDATE employees SET salary = 12000 WHERE id = 1;
-- عرض سجل التدقيق
SELECT * FROM salary_audit_log;
مثال: مُحفِّز BEFORE INSERT لملء البيانات تلقائياً (الصعوبة ⭐⭐)
إنشاء مُحفِّز يعيّن تلقائياً تاريخ الطلب والحالة الأولية عند إدراج طلب.
-- إنشاء جدول الطلبات
CREATE TABLE IF NOT EXISTS orders_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_name TEXT NOT NULL,
product_id INTEGER,
quantity INTEGER DEFAULT 1,
order_date TEXT,
status TEXT DEFAULT 'pending',
created_at TEXT
);
-- إنشاء مُحفِّز BEFORE INSERT
CREATE TRIGGER set_order_defaults
BEFORE INSERT ON orders_new
FOR EACH ROW
BEGIN
-- تعيين تاريخ الطلب تلقائياً إلى التاريخ الحالي
SET NEW.order_date = COALESCE(NEW.order_date, date('now', 'localtime'));
-- تعيين وقت الإنشاء تلقائياً
SET NEW.created_at = COALESCE(NEW.created_at, datetime('now', 'localtime'));
-- إذا كانت الكمية فارغة أو 0، الافتراضي هو 1
SET NEW.quantity = CASE WHEN NEW.quantity IS NULL OR NEW.quantity <= 0 THEN 1 ELSE NEW.quantity END;
END;
-- اختبار: إدراج بدون تحديد order_date و status
INSERT INTO orders_new (customer_name, product_id, quantity) VALUES ('John Doe', 1, 3);
-- التحقق من النتيجة المملوءة تلقائياً
SELECT * FROM orders_new;
المخرجات المتوقعة:
id customer_name product_id quantity order_date status created_at
1 John Doe 1 3 2026-06-28 pending 2026-06-28 10:30:00
SET NEW.column = value، بينما PostgreSQL يتطلب استخدام NEW.column := value في مُحفِّزات BEFORE لتعديل القيم الجديدة.
سيناريوهات التطبيق
السيناريو 1: تحديث جدول ملخص الطلبات تلقائياً
-- نسخة SQLite
-- إنشاء جدول ملخص الطلبات
CREATE TABLE IF NOT EXISTS order_summary (
customer_id INTEGER PRIMARY KEY,
total_orders INTEGER DEFAULT 0,
total_amount REAL DEFAULT 0,
last_order_date TEXT
);
-- إنشاء مُحفِّز: تحديث الملخص تلقائياً عند إدراج طلب
CREATE TRIGGER update_order_summary_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT OR REPLACE INTO order_summary (customer_id, total_orders, total_amount, last_order_date)
SELECT
NEW.customer_id,
COALESCE(os.total_orders, 0) + 1,
COALESCE(os.total_amount, 0) + NEW.amount,
NEW.order_date
FROM (SELECT 1) AS dummy
LEFT JOIN order_summary os ON os.customer_id = NEW.customer_id;
END;
-- إنشاء مُحفِّز: تحديث الملخص تلقائياً عند حذف طلب
CREATE TRIGGER update_order_summary_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
UPDATE order_summary
SET
total_orders = total_orders - 1,
total_amount = total_amount - OLD.amount
WHERE customer_id = OLD.customer_id;
-- إذا وصل عدد الطلبات إلى 0، احذف سجل الملخص
DELETE FROM order_summary
WHERE customer_id = OLD.customer_id AND total_orders <= 0;
END;
السيناريو 2: التحقق من صحة البيانات والملء التلقائي
-- نسخة SQLite: حساب إجمالي الطلب تلقائياً
CREATE TRIGGER calculate_order_total
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
-- تعيين وقت الإنشاء تلقائياً
SELECT NEW.created_at IS NULL THEN
SET NEW.created_at = datetime('now', 'localtime');
END IF;
-- التحقق من أن المبلغ يجب أن يكون موجباً
IF NEW.amount < 0 THEN
SELECT RAISE(ABORT, 'لا يمكن أن يكون مبلغ الطلب سالباً');
END IF;
END;
❓ أسئلة شائعة
س: هل تؤثر المُحفِّزات على أداء قاعدة البيانات؟ ج: نعم. كل تنشيط مُحفِّز يُنفِّذ عمليات SQL إضافية. المُحفِّزات الكثيرة أو المنطق المعقد يمكن أن تُبطئ أداء الكتابة. حافظ على منطق المُحفِّز بسيطاً وتجنب العمليات المُستهلكة للوقت داخل المُحفِّزات.
س: هل يمكن أن يحتوي جدول على عدة مُحفِّزات؟ ج: SQLite يسمح بمُحفِّز واحد لكل حدث وتوقيت. MySQL و PostgreSQL يسمحان بعدة مُحفِّزات ويتيحان تحديد ترتيب التنفيذ باستخدام FOLLOWS/PRECEDES.
س: هل يمكن للمُحفِّزات الوصول إلى جداول أخرى؟ ج: نعم. يمكن للمُحفِّزات الاستعلام عن بيانات في جداول أخرى وتعديلها، لكن احذر من تجنب المُحفِّزات الدائرية والجمود.
س: كيف أُنقّح مُحفِّزاً؟ ج: يمكنك إدراج بيانات اختبار مؤقتة في جدول سجل داخل المُحفِّز لمراقبة تدفق التنفيذ. MySQL يمكنه استخدام جملة SIGNAL لرمي رسائل خطأ مخصصة.
📖 ملخص
في هذا الدرس تعلمنا:
- مفهوم وآلية عمل المُحفِّزات
- الفرق بين توقيتي BEFORE و AFTER
- كيفية استخدام مراجع NEW و OLD
- صيغة وأمثلة المُحفِّزات في SQLite
- حالات استخدام المُحفِّزات النموذجية (التدقيق، التحديثات المتتالية، التحقق من صحة البيانات)
- مفهوم مُجدول الأحداث (معرفة موسعة من MySQL)
📝 تمارين
-
تمرين أساسي: أنشئ مُحفِّزاً يُنسخ تلقائياً معلومات الموظف إلى جدول
employees_backupعند حذف الموظف. -
تمرين متوسط: أنشئ نظام مُحفِّزات يُنفذ ما يلي:
- عند إدراج طلب جديد، خصم مخزون المنتج تلقائياً
- عند عدم كفاية المخزون، منع إدراج الطلب وإظهار خطأ
-
سؤال تفكير: كلا من المُحفِّزات وكود التطبيق يمكنهما تنفيذ منطق الأعمال. أيهما أفضل للسيناريوهات المختلفة؟ كيف تختار?
الدرس التالي → 24-practice-advanced.md



