الإجراءات المخزنة (Stored Procedures)

الإجراءات المخزنة (Stored Procedures) هي مجموعة من أوامر SQL المحفوظة في قاعدة البيانات والتي يمكن استدعاؤها وتنفيذها كوحدة واحدة. تشبه الإجراءات المخزنة الدوال (Functions) في لغات البرمجة، حيث يمكنك كتابة منطق معقد مرة واحدة ثم استخدامه في أي وقت دون إعادة كتابته. الإجراءات المخزنة هي أداة قوية جداً لأتمتة المهام المتكررة، تحسين الأداء، تعزيز الأمان، وتنظيم منطق العمل (Business Logic) في قاعدة البيانات. في هذا الدرس الشامل والمفصل من سلسلة تعلم لغة SQL باللغة العربية، سنتعلم كل شيء عن الإجراءات المخزنة: ما هي، كيفية إنشائها واستدعائها وحذفها، المعاملات (Parameters) بأنواعها المختلفة، المتغيرات، الشروط، الحلقات، معالجة الأخطاء، وأمثلة عملية تطبيقية مفصلة.

1. ما هي الإجراءات المخزنة (Stored Procedures)؟

الإجراء المخزن (Stored Procedure) هو مجموعة من أوامر SQL محفوظة في قاعدة البيانات تحت اسم معين، يمكن استدعاؤها وتنفيذها في أي وقت. بدلاً من إرسال عدة استعلامات منفصلة من التطبيق إلى قاعدة البيانات، يمكنك استدعاء إجراء واحد يقوم بتنفيذ جميع العمليات المطلوبة.

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

الإجراءات المخزنة تُكتب مرة واحدة، تُحفظ في قاعدة البيانات، ثم تُستدعى عند الحاجة. هذا يوفر الوقت، يقلل من الأخطاء، ويحسن الأداء لأن قاعدة البيانات تقوم بتجميع (Compile) الإجراء مرة واحدة وتحفظه في ذاكرة التخزين المؤقت.

2. فوائد استخدام الإجراءات المخزنة

1. تحسين الأداء (Performance)

الإجراءات المخزنة يتم تجميعها مرة واحدة وحفظها في صورة مُجمَّعة. عند الاستدعاء، لا تحتاج قاعدة البيانات لتحليل وتجميع الأوامر من جديد، مما يوفر الوقت ويحسن الأداء بشكل ملحوظ.

2. تقليل حركة البيانات على الشبكة (Network Traffic)

بدلاً من إرسال عدة استعلامات منفصلة من التطبيق إلى قاعدة البيانات، ترسل استدعاء واحد للإجراء المخزن. هذا يقلل بشكل كبير من حركة البيانات على الشبكة.

3. إعادة الاستخدام (Reusability)

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

4. تحسين الأمان (Security)

يمكنك منح المستخدمين صلاحية تنفيذ إجراء معين دون منحهم صلاحية مباشرة على الجداول. هذا يحد من الوصول المباشر للبيانات ويقلل من مخاطر الهجمات مثل SQL Injection.

5. الصيانة الأسهل (Easier Maintenance)

عندما يكون منطق العمل محفوظاً في قاعدة البيانات، يسهل تعديله وصيانته دون الحاجة لتعديل كود التطبيق وإعادة نشره.

6. ضمان سلامة البيانات (Data Integrity)

يمكنك فرض قواعد عمل معقدة في الإجراءات المخزنة لضمان أن جميع العمليات تتم بشكل صحيح ومتسق.

3. إنشاء إجراء مخزن بسيط: CREATE PROCEDURE

لإنشاء إجراء مخزن في MySQL، نستخدم أمر CREATE PROCEDURE.

الصيغة الأساسية
الصيغة الأساسية لإنشاء إجراء مخزن
DELIMITER //

CREATE PROCEDURE procedure_name()
BEGIN
    -- أوامر SQL هنا
END //

DELIMITER ;

ملاحظة مهمة: نستخدم DELIMITER لتغيير الفاصل المؤقت من ; إلى // لأن الإجراء المخزن يحتوي على عدة أوامر تنتهي بـ ;، ونحتاج طريقة لتمييز نهاية الإجراء بالكامل.

مثال بسيط: عرض جميع الموظفين
إجراء بسيط بدون معاملات
DELIMITER //

CREATE PROCEDURE get_all_employees()
BEGIN
    SELECT 
        employee_id,
        first_name,
        last_name,
        email,
        department
    FROM employees
    ORDER BY last_name;
END //

DELIMITER ;
استدعاء الإجراء المخزن: CALL
استدعاء الإجراء
-- استدعاء الإجراء
CALL get_all_employees();

4. المعاملات (Parameters): IN, OUT, INOUT

الإجراءات المخزنة يمكن أن تقبل معاملات (Parameters) لجعلها أكثر مرونة. هناك ثلاثة أنواع من المعاملات:

1. معامل الإدخال (IN Parameter)

معامل IN يُستخدم لتمرير قيمة إلى الإجراء. هذا هو النوع الافتراضي.

إجراء مع معامل IN
DELIMITER //

CREATE PROCEDURE get_employees_by_department(IN dept_name VARCHAR(50))
BEGIN
    SELECT 
        employee_id,
        first_name,
        last_name,
        email,
        salary
    FROM employees
    WHERE department = dept_name
    ORDER BY last_name;
END //

DELIMITER ;

-- استدعاء الإجراء مع تمرير قيمة
CALL get_employees_by_department('IT');
CALL get_employees_by_department('Sales');
2. معامل الإخراج (OUT Parameter)

معامل OUT يُستخدم لإرجاع قيمة من الإجراء إلى المستدعي.

إجراء مع معامل OUT
DELIMITER //

CREATE PROCEDURE count_employees_in_department(
    IN dept_name VARCHAR(50),
    OUT emp_count INT
)
BEGIN
    SELECT COUNT(*) INTO emp_count
    FROM employees
    WHERE department = dept_name;
END //

DELIMITER ;

-- استدعاء الإجراء
CALL count_employees_in_department('IT', @total);

-- عرض النتيجة
SELECT @total AS total_employees;
3. معامل الإدخال والإخراج (INOUT Parameter)

معامل INOUT يُستخدم لتمرير قيمة إلى الإجراء وإرجاع قيمة معدلة.

إجراء مع معامل INOUT
DELIMITER //

CREATE PROCEDURE increase_salary(INOUT salary DECIMAL(10, 2), IN percentage DECIMAL(5, 2))
BEGIN
    SET salary = salary + (salary * percentage / 100);
END //

DELIMITER ;

-- استخدام الإجراء
SET @current_salary = 5000;
CALL increase_salary(@current_salary, 10);
SELECT @current_salary; -- النتيجة: 5500
إجراء مع معاملات متعددة
إجراء مع معاملات IN و OUT
DELIMITER //

CREATE PROCEDURE get_department_stats(
    IN dept_name VARCHAR(50),
    OUT total_employees INT,
    OUT avg_salary DECIMAL(10, 2),
    OUT max_salary DECIMAL(10, 2)
)
BEGIN
    SELECT 
        COUNT(*),
        AVG(salary),
        MAX(salary)
    INTO total_employees, avg_salary, max_salary
    FROM employees
    WHERE department = dept_name;
END //

DELIMITER ;

-- استدعاء الإجراء
CALL get_department_stats('IT', @total, @avg, @max);

-- عرض النتائج
SELECT @total AS total, @avg AS average, @max AS maximum;

5. المتغيرات في الإجراءات المخزنة

يمكنك تعريف متغيرات محلية داخل الإجراء المخزن باستخدام DECLARE.

استخدام المتغيرات المحلية
DELIMITER //

CREATE PROCEDURE calculate_bonus(IN emp_id INT, OUT bonus DECIMAL(10, 2))
BEGIN
    DECLARE emp_salary DECIMAL(10, 2);
    DECLARE years_of_service INT;
    DECLARE bonus_percentage DECIMAL(5, 2);
    
    -- الحصول على راتب الموظف وسنوات الخدمة
    SELECT 
        salary,
        YEAR(CURRENT_DATE) - YEAR(hire_date)
    INTO emp_salary, years_of_service
    FROM employees
    WHERE employee_id = emp_id;
    
    -- حساب نسبة المكافأة بناءً على سنوات الخدمة
    IF years_of_service < 2 THEN
        SET bonus_percentage = 5;
    ELSEIF years_of_service < 5 THEN
        SET bonus_percentage = 10;
    ELSEIF years_of_service < 10 THEN
        SET bonus_percentage = 15;
    ELSE
        SET bonus_percentage = 20;
    END IF;
    
    -- حساب المكافأة
    SET bonus = emp_salary * bonus_percentage / 100;
END //

DELIMITER ;

-- استخدام الإجراء
CALL calculate_bonus(123, @employee_bonus);
SELECT @employee_bonus;

6. الشروط (IF, CASE) في الإجراءات المخزنة

استخدام IF...THEN...ELSE
جملة IF الشرطية
DELIMITER //

CREATE PROCEDURE categorize_employee(IN emp_id INT, OUT category VARCHAR(50))
BEGIN
    DECLARE emp_salary DECIMAL(10, 2);
    
    SELECT salary INTO emp_salary
    FROM employees
    WHERE employee_id = emp_id;
    
    IF emp_salary < 3000 THEN
        SET category = 'Junior';
    ELSEIF emp_salary < 6000 THEN
        SET category = 'Mid-Level';
    ELSEIF emp_salary < 10000 THEN
        SET category = 'Senior';
    ELSE
        SET category = 'Executive';
    END IF;
END //

DELIMITER ;
استخدام CASE
جملة CASE الشرطية
DELIMITER //

CREATE PROCEDURE get_discount_rate(IN customer_type VARCHAR(20), OUT discount DECIMAL(5, 2))
BEGIN
    SET discount = CASE customer_type
        WHEN 'VIP' THEN 20.00
        WHEN 'Premium' THEN 15.00
        WHEN 'Regular' THEN 10.00
        WHEN 'New' THEN 5.00
        ELSE 0.00
    END;
END //

DELIMITER ;

7. الحلقات (LOOP, WHILE, REPEAT) في الإجراءات المخزنة

حلقة WHILE
حلقة WHILE
DELIMITER //

CREATE PROCEDURE generate_numbers(IN max_num INT)
BEGIN
    DECLARE counter INT DEFAULT 1;
    
    DROP TABLE IF EXISTS temp_numbers;
    CREATE TEMPORARY TABLE temp_numbers (num INT);
    
    WHILE counter <= max_num DO
        INSERT INTO temp_numbers VALUES (counter);
        SET counter = counter + 1;
    END WHILE;
    
    SELECT * FROM temp_numbers;
END //

DELIMITER ;
حلقة REPEAT
حلقة REPEAT
DELIMITER //

CREATE PROCEDURE calculate_factorial(IN num INT, OUT result BIGINT)
BEGIN
    DECLARE counter INT DEFAULT 1;
    SET result = 1;
    
    REPEAT
        SET result = result * counter;
        SET counter = counter + 1;
    UNTIL counter > num
    END REPEAT;
END //

DELIMITER ;

-- حساب 5! (5 factorial)
CALL calculate_factorial(5, @fact);
SELECT @fact; -- النتيجة: 120
حلقة LOOP مع LEAVE
حلقة LOOP
DELIMITER //

CREATE PROCEDURE sum_to_n(IN n INT, OUT total INT)
BEGIN
    DECLARE counter INT DEFAULT 1;
    SET total = 0;
    
    my_loop: LOOP
        SET total = total + counter;
        SET counter = counter + 1;
        
        IF counter > n THEN
            LEAVE my_loop;
        END IF;
    END LOOP;
END //

DELIMITER ;

8. معالجة الأخطاء (Error Handling)

يمكنك معالجة الأخطاء في الإجراءات المخزنة باستخدام DECLARE ... HANDLER.

معالجة الأخطاء
DELIMITER //

CREATE PROCEDURE safe_insert_employee(
    IN emp_name VARCHAR(100),
    IN emp_email VARCHAR(100),
    OUT success BOOLEAN,
    OUT error_message VARCHAR(255)
)
BEGIN
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SET success = FALSE;
        SET error_message = 'خطأ في إدراج الموظف';
    END;
    
    DECLARE CONTINUE HANDLER FOR 1062  -- Duplicate entry error
    BEGIN
        SET success = FALSE;
        SET error_message = 'البريد الإلكتروني موجود مسبقاً';
    END;
    
    SET success = TRUE;
    SET error_message = '';
    
    INSERT INTO employees (first_name, email)
    VALUES (emp_name, emp_email);
    
    IF success THEN
        SET error_message = 'تم إدراج الموظف بنجاح';
    END IF;
END //

DELIMITER ;

9. مثال عملي شامل: نظام إدارة طلبات

لنطبق ما تعلمناه في مثال واقعي متكامل: إجراء لإنشاء طلب جديد في نظام تجارة إلكترونية.

إجراء شامل لإنشاء طلب
DELIMITER //

CREATE PROCEDURE create_order(
    IN p_customer_id INT,
    IN p_product_id INT,
    IN p_quantity INT,
    OUT p_order_id INT,
    OUT p_success BOOLEAN,
    OUT p_message VARCHAR(255)
)
BEGIN
    DECLARE v_product_price DECIMAL(10, 2);
    DECLARE v_stock_quantity INT;
    DECLARE v_total_amount DECIMAL(10, 2);
    
    -- معالج للأخطاء
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_success = FALSE;
        SET p_message = 'حدث خطأ أثناء إنشاء الطلب';
        SET p_order_id = 0;
    END;
    
    -- بدء المعاملة
    START TRANSACTION;
    
    -- التحقق من وجود المنتج والكمية المتاحة
    SELECT price, stock_quantity 
    INTO v_product_price, v_stock_quantity
    FROM products
    WHERE product_id = p_product_id;
    
    -- التحقق من توفر الكمية
    IF v_stock_quantity < p_quantity THEN
        SET p_success = FALSE;
        SET p_message = 'الكمية المطلوبة غير متوفرة في المخزون';
        SET p_order_id = 0;
        ROLLBACK;
    ELSE
        -- حساب الإجمالي
        SET v_total_amount = v_product_price * p_quantity;
        
        -- إدراج الطلب
        INSERT INTO orders (customer_id, order_date, status, total_amount)
        VALUES (p_customer_id, NOW(), 'pending', v_total_amount);
        
        SET p_order_id = LAST_INSERT_ID();
        
        -- إدراج تفاصيل الطلب
        INSERT INTO order_items (order_id, product_id, quantity, unit_price)
        VALUES (p_order_id, p_product_id, p_quantity, v_product_price);
        
        -- تحديث المخزون
        UPDATE products
        SET stock_quantity = stock_quantity - p_quantity
        WHERE product_id = p_product_id;
        
        -- تأكيد المعاملة
        COMMIT;
        
        SET p_success = TRUE;
        SET p_message = 'تم إنشاء الطلب بنجاح';
    END IF;
END //

DELIMITER ;

-- استخدام الإجراء
CALL create_order(1, 101, 2, @order_id, @success, @message);
SELECT @order_id, @success, @message;

10. عرض وحذف الإجراءات المخزنة

عرض قائمة الإجراءات المخزنة
عرض جميع الإجراءات المخزنة
-- عرض جميع الإجراءات في قاعدة البيانات الحالية
SHOW PROCEDURE STATUS WHERE Db = DATABASE();

-- عرض تعريف إجراء معين
SHOW CREATE PROCEDURE get_all_employees;

-- من جداول النظام
SELECT 
    ROUTINE_NAME,
    ROUTINE_TYPE,
    CREATED,
    LAST_ALTERED
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = DATABASE()
    AND ROUTINE_TYPE = 'PROCEDURE';
حذف إجراء مخزن
حذف إجراء مخزن
-- حذف إجراء
DROP PROCEDURE get_all_employees;

-- حذف آمن
DROP PROCEDURE IF EXISTS get_all_employees;

11. أفضل الممارسات للإجراءات المخزنة

ما يجب فعله
  • استخدم أسماء واضحة وموحدة للإجراءات
  • أضف تعليقات توضيحية للكود المعقد
  • استخدم معالجة الأخطاء دائماً
  • استخدم المعاملات (Transactions) للعمليات المتعددة
  • اختبر الإجراءات بشكل شامل قبل النشر
  • وثق المعاملات المطلوبة والقيم المُرجعة
  • استخدم معاملات OUT لإرجاع حالة النجاح والرسائل
ما يجب تجنبه
  • لا تضع كل منطق العمل في الإجراءات المخزنة
  • لا تنشئ إجراءات معقدة جداً يصعب صيانتها
  • لا تتجاهل معالجة الأخطاء
  • لا تستخدم SELECT * في الإجراءات
  • لا تنسَ استخدام DELIMITER عند الإنشاء
  • لا تترك معاملات مفتوحة دون COMMIT أو ROLLBACK
ملخص الدرس

في هذا الدرس الشامل، تعلمنا كل شيء عن الإجراءات المخزنة في SQL:

  • ما هي الإجراءات المخزنة وفوائدها
  • إنشاء إجراءات بسيطة ومعقدة
  • المعاملات: IN, OUT, INOUT
  • المتغيرات المحلية
  • الشروط: IF, CASE
  • الحلقات: WHILE, REPEAT, LOOP
  • معالجة الأخطاء
  • مثال عملي شامل لنظام طلبات
  • أفضل الممارسات

الإجراءات المخزنة هي أداة قوية جداً لأتمتة المهام المعقدة وتحسين أداء قاعدة البيانات. استخدمها بحكمة لجعل نظامك أكثر كفاءة وسهولة في الصيانة.

الخطوة التالية: المشغلات (Triggers)

أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم المشغلات (Triggers) وتطوير مهاراتك في قواعد البيانات.

الانتقال إلى الدرس التالي
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن