الإجراءات المخزنة ودوال SQL

الإجراءات المخزنة ودوال SQL

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

تخيّل أنك في بنك:

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

ما هو الإجراء المخزن

الإجراء المخزن هو مجموعة مُجمَّعة مسبقاً من جمل SQL مخزنة في قاعدة البيانات يمكن أن:

💡 SQLite لا يدعم الإجراءات المخزنة. الأمثلة أدناه توضح المفاهيم وصيغ MySQL/PostgreSQL. يمكن لمستخدمي SQLite استخدام الدوال المخصصة كبديل.

صيغة الإجراء المخزن في MySQL

SQL
-- الهيكل الأساسي للإجراء المخزن في MySQL
DELIMITER //
CREATE PROCEDURE procedure_name (parameter_list)
BEGIN
    -- تعريف المتغيرات
    -- منطق الأعمال
    -- إرجاع النتائج
END //
DELIMITER ;

صيغة الإجراء المخزن في PostgreSQL

SQL
-- الهيكل الأساسي للإجراء المخزن في PostgreSQL
CREATE OR REPLACE FUNCTION procedure_name(parameter_list)
RETURNS return_type AS $$
DECLARE
    -- تعريف المتغيرات
BEGIN
    -- منطق الأعمال
    -- جملة RETURN
END;
$$ LANGUAGE plpgsql;

الدوال المخصصة في SQLite

SQLite لا يدعم الإجراءات المخزنة، لكنه يدعم إنشاء دوال محددة من قبل المستخدم عبر لغات البرمجة:

SQL
-- SQLite نفسه لا يدعم CREATE FUNCTION
-- يجب تسجيل الدوال المخصصة عبر Python أو C أو لغات برمجة أخرى

-- مثال Python (باستخدام وحدة sqlite3)
import sqlite3

def calculate_bonus(salary, rate):
    return salary * rate

conn = sqlite3.connect('company.db')
conn.create_function('calculate_bonus', 2, calculate_bonus)

-- بعد التسجيل، يمكنك استخدامها في SQL
SELECT name, salary, calculate_bonus(salary, 0.1) AS bonus
FROM employees;

جمل التحكم في التدفق

IF/ELSE منطق شرطي

SQL
-- مثال MySQL
DELIMITER //
CREATE PROCEDURE check_salary(IN emp_id INT)
BEGIN
    DECLARE emp_salary DECIMAL(10,2);
    
    SELECT salary INTO emp_salary
    FROM employees WHERE id = emp_id;
    
    IF emp_salary > 10000 THEN
        SELECT 'راتب مرتفع' AS status;
    ELSEIF emp_salary > 5000 THEN
        SELECT 'راتب متوسط' AS status;
    ELSE
        SELECT 'راتب منخفض' AS status;
    END IF;
END //
DELIMITER ;

WHILE حلقة

SQL
-- مثال MySQL: حساب المجموع من 1 إلى N
DELIMITER //
CREATE PROCEDURE calculate_sum(IN n INT, OUT total INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    SET total = 0;
    
    WHILE i <= n DO
        SET total = total + i;
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

LOOP حلقة

SQL
-- مثال MySQL: LOOP مع LEAVE
DELIMITER //
CREATE PROCEDURE process_employees()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE emp_name VARCHAR(100);
    
    DECLARE cur CURSOR FOR SELECT name FROM employees;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN cur;
    
    read_loop: LOOP
        FETCH cur INTO emp_name;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- معالجة كل سجل
        SELECT CONCAT('معالجة: ', emp_name);
    END LOOP;
    
    CLOSE cur;
END //
DELIMITER ;

مفهوم المؤشرات (Cursors)

تُستخدم المؤشرات لمعالجة مجموعات النتائج صف بصف:

SQL
-- تدفق استخدام المؤشر الأساسي
-- 1. تعريف المؤشر
DECLARE cur CURSOR FOR SELECT column FROM table;

-- 2. فتح المؤشر
OPEN cur;

-- 3. جلب البيانات
FETCH cur INTO variable;

-- 4. معالجة البيانات (عادةً في حلقة)

-- 5. إغلاق المؤشر
CLOSE cur;

الدوال المخزنة

الدوال المخزنة مشابهة للإجراءات المخزنة لكن لديها اختلافات مهمة:

SQL
-- دالة مخزنة في MySQL
DELIMITER //
CREATE FUNCTION get_annual_salary(monthly_salary DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN monthly_salary * 12;
END //
DELIMITER ;

-- استخدام الدالة
SELECT name, get_annual_salary(salary) AS annual_salary
FROM employees;

الإجراءات المخزنة مقابل الدوال

الميزة الإجراء المخزن الدالة المخزنة
طريقة الاستدعاء جملة CALL تُستدعى مباشرة في SQL
قيمة الإرجاع يمكن أن تُرجع عدة مجموعات نتائج يجب أن تُرجع قيمة واحدة
حالة الاستخدام منطق أعمال معقد حساب وتحويل
التحكم في المعاملات يمكنها إدارة المعاملات لا يمكنها إدارة المعاملات
في جمل SQL لا يمكن استخدامها في SELECT يمكن استخدامها في SELECT

الأساسيات

إنشاء إجراء مخزن (MySQL)

SQL
-- إنشاء إجراء مخزن مع مُعامِلات
DELIMITER //
CREATE PROCEDURE get_employees_by_dept(
    IN dept_name VARCHAR(50),
    IN min_salary DECIMAL(10,2)
)
BEGIN
    SELECT e.name, e.salary, d.department_name
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    WHERE d.department_name = dept_name
    AND e.salary >= min_salary
    ORDER BY e.salary DESC;
END //
DELIMITER ;

-- استدعاء الإجراء المخزن
CALL get_employees_by_dept('Tech', 8000);

إنشاء دالة مخزنة (PostgreSQL)

SQL
-- إنشاء دالة لحساب المكافأة
CREATE OR REPLACE FUNCTION calculate_bonus(
    base_salary NUMERIC,
    performance_rating INTEGER
)
RETURNS NUMERIC AS $$
DECLARE
    bonus_rate NUMERIC;
BEGIN
    CASE performance_rating
        WHEN 1 THEN bonus_rate := 0.10;
        WHEN 2 THEN bonus_rate := 0.15;
        WHEN 3 THEN bonus_rate := 0.20;
        WHEN 4 THEN bonus_rate := 0.25;
        WHEN 5 THEN bonus_rate := 0.30;
        ELSE bonus_rate := 0.05;
    END CASE;
    
    RETURN base_salary * bonus_rate;
END;
$$ LANGUAGE plpgsql;

-- استخدام الدالة
SELECT 
    name,
    salary,
    performance_rating,
    calculate_bonus(salary, performance_rating) AS bonus
FROM employees;
💡 نصيحة: الإجراءات المخزنة مناسبة لتغليف منطق الأعمال المعقد، بينما الدوال مناسبة للحسابات القابلة لإعادة الاستخدام. اختر النهج المناسب بناءً على الاحتياجات الفعلية.

أمثلة

مثال: إنشاء إجراء مخزن لتعيين موظف جديد (الصعوبة ⭐⭐⭐)

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

SQL
-- نسخة MySQL
DELIMITER //
CREATE PROCEDURE hire_employee(
    IN p_name VARCHAR(100),
    IN p_department_id INT,
    IN p_position VARCHAR(50),
    IN p_salary DECIMAL(10,2),
    OUT p_employee_id INT,
    OUT p_message VARCHAR(200)
)
BEGIN
    DECLARE dept_exists INT DEFAULT 0;
    DECLARE max_id INT DEFAULT 0;
    
    -- التحقق من وجود القسم
    SELECT COUNT(*) INTO dept_exists
    FROM departments WHERE id = p_department_id;
    
    IF dept_exists = 0 THEN
        SET p_message = 'خطأ: القسم المحدد غير موجود';
        SET p_employee_id = -1;
    ELSE
        -- الحصول على أعلى رقم موظف
        SELECT COALESCE(MAX(id), 0) INTO max_id FROM employees;
        SET p_employee_id = max_id + 1;
        
        -- إدراج سجل الموظف الجديد
        INSERT INTO employees (id, name, department_id, position, salary, hire_date)
        VALUES (p_employee_id, p_name, p_department_id, p_position, p_salary, CURDATE());
        
        SET p_message = CONCAT('نجاح: تم تعيين الموظف ', p_name, ' بالرقم ', p_employee_id);
    END IF;
END //
DELIMITER ;

-- مثال الاستدعاء
CALL hire_employee('John Doe', 1, 'مهندس مبتدئ', 8000.00, @new_id, @msg);
SELECT @new_id AS employee_id, @msg AS message;
▶ جرّب الكود

مثال: نسخة PostgreSQL — دالة تعديل راتب الموظف (الصعوبة ⭐⭐⭐)

SQL
-- نسخة PostgreSQL
CREATE OR REPLACE FUNCTION adjust_salary(
    p_employee_id INTEGER,
    p_adjustment_type VARCHAR(10),  -- 'RAISE' أو 'REDUCE'
    p_percentage NUMERIC
)
RETURNS TABLE(
    employee_name VARCHAR,
    old_salary NUMERIC,
    new_salary NUMERIC,
    adjustment_amount NUMERIC
) AS $$
DECLARE
    v_old_salary NUMERIC;
    v_new_salary NUMERIC;
    v_name VARCHAR;
BEGIN
    -- الحصول على الراتب الحالي
    SELECT name, salary INTO v_name, v_old_salary
    FROM employees WHERE id = p_employee_id;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'رقم الموظف % غير موجود', p_employee_id;
    END IF;
    
    -- حساب الراتب الجديد
    IF p_adjustment_type = 'RAISE' THEN
        v_new_salary := v_old_salary * (1 + p_percentage / 100);
    ELSE
        v_new_salary := v_old_salary * (1 - p_percentage / 100);
    END IF;
    
    -- تحديث قاعدة البيانات
    UPDATE employees SET salary = v_new_salary WHERE id = p_employee_id;
    
    -- إرجاع النتيجة
    employee_name := v_name;
    old_salary := v_old_salary;
    new_salary := v_new_salary;
    adjustment_amount := v_new_salary - v_old_salary;
    
    RETURN NEXT;
END;
$$ LANGUAGE plpgsql;

-- مثال الاستخدام
SELECT * FROM adjust_salary(1, 'RAISE', 10);
▶ جرّب الكود

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

السيناريو 1: معالجة البيانات دفعة واحدة

SQL
-- MySQL: تحديث حالة الطلبات دفعة واحدة
DELIMITER //
CREATE PROCEDURE update_order_status()
BEGIN
    DECLARE affected_rows INT DEFAULT 0;
    
    -- بدء المعاملة
    START TRANSACTION;
    
    -- إلغاء الطلبات المعلقة لأكثر من 30 يوماً
    UPDATE orders 
    SET status = 'cancelled', 
        updated_at = NOW()
    WHERE status = 'pending' 
    AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
    
    SET affected_rows = ROW_COUNT();
    
    -- تسجيل العملية
    INSERT INTO process_log (process_name, affected_rows, executed_at)
    VALUES ('update_order_status', affected_rows, NOW());
    
    -- الالتزام بالمعاملة
    COMMIT;
    
    SELECT CONCAT('تم إلغاء ', affected_rows, ' طلب منتهي الصلاحية') AS result;
END //
DELIMITER ;

السيناريو 2: إنشاء تقارير معقدة

SQL
-- PostgreSQL: إنشاء تقرير رواتب الأقسام
CREATE OR REPLACE FUNCTION generate_salary_report()
RETURNS TABLE(
    department_name VARCHAR,
    employee_count BIGINT,
    avg_salary NUMERIC,
    min_salary NUMERIC,
    max_salary NUMERIC,
    total_salary NUMERIC
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        d.department_name,
        COUNT(e.id) AS employee_count,
        ROUND(AVG(e.salary), 2) AS avg_salary,
        MIN(e.salary) AS min_salary,
        MAX(e.salary) AS max_salary,
        SUM(e.salary) AS total_salary
    FROM departments d
    LEFT JOIN employees e ON d.id = e.department_id
    GROUP BY d.department_name
    ORDER BY total_salary DESC;
END;
$$ LANGUAGE plpgsql;

-- الاستخدام
SELECT * FROM generate_salary_report();

❓ أسئلة شائعة

س: SQLite لا يدعم الإجراءات المخزنة نهائياً — ماذا أفعل؟ ج: SQLite مُصمَّم كقاعدة بيانات خفيفة مدمجة ولا يدعم الإجراءات المخزنة. يمكنك تنفيذ منطق الأعمال باستخدام لغات البرمجة (Python، Java، إلخ)، أو استخدام العروض (VIEW) لتغليف الاستعلامات المعقدة.

س: ما الفرق بين الإجراء المخزن والعرض؟ ج: الععرض هو جدول افتراضي، يُستخدم أساساً لتبسيط الاستعلامات؛ الإجراء المخزن هو برنامج يمكن أن يحتوي على منطق أعمال معقد، تحكم في التدفق، وعمليات تعديل بيانات.

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

س: كيف أُنقّح إجراءً مخزناً؟ ج: MySQL يمكنه استخدام جمل SELECT لإخراج النتائج الوسيطة؛ PostgreSQL يمكنه استخدام RAISE NOTICE لإخراج معلومات التصحيح؛ يمكنك أيضاً استخدام ميزات التصحيح في أدوات إدارة قواعد البيانات.

📖 ملخص

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

📝 تمارين

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

  2. تمرين متوسط: أنشئ دالة تحسب بدل الأقدمية بناءً على سنوات خدمة الموظف (من hire_date إلى التاريخ الحالي):

    • 1-3 سنوات: 5% من الراتب الأساسي
    • 3-5 سنوات: 10% من الراتب الأساسي
    • أكثر من 5 سنوات: 15% من الراتب الأساسي
  3. سؤال تفكير: قارن بين إيجابيات وسلبيات معالجة منطق الأعمال في كود طبقة التطبيق مقابل الإجراءات المخزنة في قاعدة البيانات.


الدرس التالي → 23-triggers-events.md

Web-Tutorial.com

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

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

100%