المُحفِّزات والأحداث

المُحفِّزات والأحداث

تشبيه من الحياة الواقعية

تخيّل نظام منزل ذكي:

المفاهيم الأساسية

ما هو المُحفِّز

المُحفِّز هو كائن قاعدة بيانات مرتبط بجدول يتم تنفيذه تلقائياً عند حدوث حدث معين على ذلك الجدول:

توقيت المُحفِّز: BEFORE مقابل AFTER

التوقيت الوصف الاستخدام النموذجي
BEFORE يُفعَّل قبل تنفيذ العملية التحقق من صحة البيانات، التعديل التلقائي للقيم
AFTER يُفعَّل بعد تنفيذ العملية سجلات التدقيق، التحديثات المتتالية

أنواع أحداث المُحفِّز

مراجع NEW و OLD

يمكنك الوصول إلى البيانات التي تتم معالجتها داخل المُحفِّز:

الحدث NEW OLD
INSERT الصف المُدرج حديثاً غير متاح
UPDATE القيمة بعد التحديث القيمة قبل التحديث
DELETE غير متاح الصف المحذوف
SQL
-- مثال SQLite: الوصول إلى NEW و OLD
-- NEW.column_name: يشير إلى البيانات الجديدة
-- OLD.column_name: يشير إلى البيانات القديمة

حالات استخدام المُحفِّزات النموذجية

  1. سجلات التدقيق: تسجيل من عدّل أي بيانات ومتى
  2. التحديثات المتتالية: تحديث البيانات في الجداول ذات الصلة تلقائياً
  3. التحقق من صحة البيانات: إجراء التحقق من قواعد الأعمال المعقدة قبل كتابة البيانات
  4. الحساب التلقائي: حساب قيم الحقول المشتقة تلقائياً
  5. الاحتفاظ بالبيانات المكررة: تحديث جداول الملخص أو ذاكرة التخزين المؤقت بشكل متزامن

معرفة موسعة: مُجدول الأحداث في قواعد البيانات الأخرى

⚠️ ملاحظة: SQLite لا يدعم مُجدول الأحداث. المحتوى التالي هو معرفة موسعة تُظهر صيغة مُجدول الأحداث في MySQL كمرجع.

يتيح مُجدول الأحداث تنفيذ المهام تلقائياً وفقاً لجدول زمني:

SQL
-- مثال مُجدول الأحداث في 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 يدعم المُحفِّزات بالصيغة التالية:

SQL
-- صيغة إنشاء المُحفِّز الأساسية
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
    -- منطق المُحفِّز
END;

صيغة المُحفِّز في MySQL

SQL
-- صيغة المُحفِّز في MySQL
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
    -- منطق المُحفِّز
    -- استخدم NEW و OLD للإشارة إلى البيانات
END //
DELIMITER ;

صيغة المُحفِّز في PostgreSQL

SQL
-- 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();
💡 نصيحة: المُحفِّزات تُضيف تعقيداً لعمليات قاعدة البيانات. استخدمها بحذر وتجنب إنشاء منطق مُحفِّز معقد للغاية.

أمثلة

مثال: إنشاء مُحفِّز سجل تدقيق (الصعوبة ⭐⭐⭐)

إنشاء مُحفِّز يسجل تلقائياً تاريخ تعديل رواتب الموظفين.

SQL
-- نسخة 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 لملء البيانات تلقائياً (الصعوبة ⭐⭐)

إنشاء مُحفِّز يعيّن تلقائياً تاريخ الطلب والحالة الأولية عند إدراج طلب.

SQL
-- إنشاء جدول الطلبات
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;
▶ جرّب الكود

المخرجات المتوقعة:

TEXT
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
💡 قيود SQLite: صيغة المُحفِّز في SQLite تختلف عن MySQL/PostgreSQL. MySQL يدعم SET NEW.column = value، بينما PostgreSQL يتطلب استخدام NEW.column := value في مُحفِّزات BEFORE لتعديل القيم الجديدة.

سيناريوهات التطبيق

السيناريو 1: تحديث جدول ملخص الطلبات تلقائياً

SQL
-- نسخة 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: التحقق من صحة البيانات والملء التلقائي

SQL
-- نسخة 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 لرمي رسائل خطأ مخصصة.

📖 ملخص

في هذا الدرس تعلمنا:

📝 تمارين

  1. تمرين أساسي: أنشئ مُحفِّزاً يُنسخ تلقائياً معلومات الموظف إلى جدول employees_backup عند حذف الموظف.

  2. تمرين متوسط: أنشئ نظام مُحفِّزات يُنفذ ما يلي:

    • عند إدراج طلب جديد، خصم مخزون المنتج تلقائياً
    • عند عدم كفاية المخزون، منع إدراج الطلب وإظهار خطأ
  3. سؤال تفكير: كلا من المُحفِّزات وكود التطبيق يمكنهما تنفيذ منطق الأعمال. أيهما أفضل للسيناريوهات المختلفة؟ كيف تختار?


الدرس التالي → 24-practice-advanced.md

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%