الإجراءات المخزنة (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 يُستخدم لتمرير قيمة إلى الإجراء. هذا هو النوع الافتراضي.
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 يُستخدم لإرجاع قيمة من الإجراء إلى المستدعي.
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 يُستخدم لتمرير قيمة إلى الإجراء وإرجاع قيمة معدلة.
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
إجراء مع معاملات متعددة
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
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
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
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
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
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) وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي