الدوال الشرطية (Conditional Functions)

مقدمة حول الدوال الشرطية في SQL

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

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

لماذا الدوال الشرطية مهمة؟

  • تصنيف البيانات ديناميكياً (مثل: منخفض، متوسط، مرتفع)
  • معالجة القيم الفارغة (NULL) بشكل احترافي
  • إنشاء أعمدة محسوبة بناءً على شروط معقدة
  • تبسيط الاستعلامات المعقدة
  • تحسين قابلية قراءة التقارير

تعبير CASE - المنطق الشرطي المتقدم

تعبير CASE في SQL يشبه if-else في لغات البرمجة الأخرى. يسمح لك بتقييم شروط متعددة وإرجاع قيم مختلفة بناءً على هذه الشروط.

الصيغة الأساسية - Simple CASE:

CASE expression
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ...
    ELSE default_result
END

الصيغة المتقدمة - Searched CASE:

CASE
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ...
    ELSE default_result
END

مثال 1 - تصنيف الأسعار:

CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100),
    price DECIMAL(10, 2),
    stock_quantity INT
);

INSERT INTO products (product_name, price, stock_quantity) VALUES
('لابتوب', 3500.00, 15),
('هاتف ذكي', 2200.00, 30),
('سماعات', 450.00, 50),
('ماوس', 80.00, 100),
('لوحة مفاتيح', 150.00, 75);

-- تصنيف المنتجات حسب السعر
SELECT 
    product_name,
    price,
    CASE 
        WHEN price >= 3000 THEN 'فاخر جداً'
        WHEN price >= 1000 THEN 'فاخر'
        WHEN price >= 500 THEN 'متوسط'
        WHEN price >= 100 THEN 'اقتصادي'
        ELSE 'رخيص'
    END AS الفئة_السعرية
FROM products
ORDER BY price DESC;
النتيجة:
product_name price الفئة_السعرية
لابتوب 3500.00 فاخر جداً
هاتف ذكي 2200.00 فاخر
سماعات 450.00 اقتصادي
لوحة مفاتيح 150.00 اقتصادي
ماوس 80.00 رخيص

مثال 2 - تصنيف المخزون:

-- تحديد حالة المخزون
SELECT 
    product_name,
    stock_quantity,
    CASE 
        WHEN stock_quantity = 0 THEN 'نفذ من المخزون'
        WHEN stock_quantity < 20 THEN 'مخزون منخفض'
        WHEN stock_quantity < 50 THEN 'مخزون متوسط'
        ELSE 'مخزون جيد'
    END AS حالة_المخزون,
    CASE 
        WHEN stock_quantity < 20 THEN 'عاجل: إعادة الطلب'
        WHEN stock_quantity < 50 THEN 'مراقبة'
        ELSE 'طبيعي'
    END AS الإجراء_المطلوب
FROM products;

مثال 3 - Simple CASE (المقارنة المباشرة):

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_name VARCHAR(100),
    status VARCHAR(20),
    total_amount DECIMAL(10, 2)
);

INSERT INTO orders (customer_name, status, total_amount) VALUES
('أحمد', 'pending', 500.00),
('فاطمة', 'shipped', 750.00),
('خالد', 'delivered', 1200.00),
('سارة', 'cancelled', 300.00);

-- ترجمة حالة الطلب
SELECT 
    order_id,
    customer_name,
    status,
    CASE status
        WHEN 'pending' THEN 'قيد الانتظار'
        WHEN 'processing' THEN 'قيد المعالجة'
        WHEN 'shipped' THEN 'تم الشحن'
        WHEN 'delivered' THEN 'تم التسليم'
        WHEN 'cancelled' THEN 'ملغي'
        ELSE 'غير معروف'
    END AS الحالة_بالعربي,
    total_amount
FROM orders;

استخدامات متقدمة لـ CASE

1. استخدام CASE في ORDER BY:

-- ترتيب مخصص حسب الحالة
SELECT 
    order_id,
    customer_name,
    status,
    total_amount
FROM orders
ORDER BY 
    CASE status
        WHEN 'pending' THEN 1
        WHEN 'processing' THEN 2
        WHEN 'shipped' THEN 3
        WHEN 'delivered' THEN 4
        WHEN 'cancelled' THEN 5
    END;

2. استخدام CASE في WHERE:

-- تصفية ديناميكية
SELECT 
    product_name,
    price,
    stock_quantity
FROM products
WHERE 
    CASE 
        WHEN price > 1000 THEN stock_quantity > 10
        ELSE stock_quantity > 20
    END;

3. استخدام CASE في GROUP BY:

-- تجميع حسب فئات مخصصة
SELECT 
    CASE 
        WHEN price < 500 THEN 'رخيص'
        WHEN price < 2000 THEN 'متوسط'
        ELSE 'غالي'
    END AS الفئة,
    COUNT(*) AS عدد_المنتجات,
    AVG(price) AS متوسط_السعر,
    SUM(stock_quantity) AS إجمالي_المخزون
FROM products
GROUP BY 
    CASE 
        WHEN price < 500 THEN 'رخيص'
        WHEN price < 2000 THEN 'متوسط'
        ELSE 'غالي'
    END;

4. CASE متداخل (Nested CASE):

-- تصنيف متعدد المستويات
SELECT 
    product_name,
    price,
    stock_quantity,
    CASE 
        WHEN price > 1000 THEN
            CASE 
                WHEN stock_quantity > 20 THEN 'فاخر - مخزون جيد'
                WHEN stock_quantity > 10 THEN 'فاخر - مخزون متوسط'
                ELSE 'فاخر - مخزون منخفض'
            END
        ELSE
            CASE 
                WHEN stock_quantity > 50 THEN 'عادي - مخزون جيد'
                WHEN stock_quantity > 20 THEN 'عادي - مخزون متوسط'
                ELSE 'عادي - مخزون منخفض'
            END
    END AS التصنيف_الكامل
FROM products;

دالة IF() - الشرط البسيط

دالة IF() في MySQL توفر طريقة مختصرة لكتابة شرط بسيط بدلاً من استخدام CASE.

الصيغة الأساسية:

IF(condition, value_if_true, value_if_false)

أمثلة عملية:

-- مثال بسيط
SELECT 
    product_name,
    price,
    IF(price > 1000, 'غالي', 'رخيص') AS التصنيف
FROM products;

-- تحديد الخصم
SELECT 
    product_name,
    price,
    IF(price > 2000, 0.20, 0.10) AS نسبة_الخصم,
    ROUND(price * IF(price > 2000, 0.80, 0.90), 2) AS السعر_بعد_الخصم
FROM products;

-- التحقق من المخزون
SELECT 
    product_name,
    stock_quantity,
    IF(stock_quantity > 0, 'متوفر', 'غير متوفر') AS التوفر
FROM products;

IF متداخل:

-- يمكن تداخل IF لكن CASE أفضل للشروط المتعددة
SELECT 
    product_name,
    price,
    IF(price > 2000, 'فاخر جداً',
        IF(price > 1000, 'فاخر',
            IF(price > 500, 'متوسط', 'اقتصادي')
        )
    ) AS الفئة
FROM products;
ملاحظة: دالة IF() متوفرة في MySQL و MariaDB، لكنها غير متوفرة في PostgreSQL أو SQL Server. استخدم CASE للتوافق مع جميع قواعد البيانات.

دالتا IFNULL() و COALESCE() - معالجة القيم الفارغة

دالة IFNULL() - استبدال NULL بقيمة افتراضية

دالة IFNULL() تفحص قيمة، وإذا كانت NULL تُرجع قيمة بديلة.

IFNULL(expression, alternative_value)
CREATE TABLE employees (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    employee_name VARCHAR(100),
    salary DECIMAL(10, 2),
    bonus DECIMAL(10, 2),
    department VARCHAR(50)
);

INSERT INTO employees (employee_name, salary, bonus, department) VALUES
('أحمد محمد', 5000.00, 500.00, 'المبيعات'),
('فاطمة علي', 6000.00, NULL, 'التسويق'),
('خالد حسن', 5500.00, 300.00, 'الدعم الفني'),
('سارة أحمد', 7000.00, NULL, NULL);

-- استخدام IFNULL لمعالجة القيم الفارغة
SELECT 
    employee_name,
    salary,
    bonus,
    IFNULL(bonus, 0) AS المكافأة_الفعلية,
    salary + IFNULL(bonus, 0) AS الراتب_الإجمالي,
    IFNULL(department, 'غير محدد') AS القسم
FROM employees;
النتيجة:
employee_name salary bonus المكافأة_الفعلية الراتب_الإجمالي القسم
أحمد محمد 5000.00 500.00 500.00 5500.00 المبيعات
فاطمة علي 6000.00 NULL 0.00 6000.00 التسويق
خالد حسن 5500.00 300.00 300.00 5800.00 الدعم الفني
سارة أحمد 7000.00 NULL 0.00 7000.00 غير محدد

دالة COALESCE() - اختيار أول قيمة غير NULL

دالة COALESCE() تقبل عدة معاملات وتُرجع أول قيمة غير NULL.

COALESCE(value1, value2, value3, ..., default_value)
CREATE TABLE contacts (
    contact_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    mobile_phone VARCHAR(20),
    home_phone VARCHAR(20),
    work_phone VARCHAR(20),
    email VARCHAR(100)
);

INSERT INTO contacts (name, mobile_phone, home_phone, work_phone, email) VALUES
('أحمد', '0501234567', NULL, NULL, 'ahmed@example.com'),
('فاطمة', NULL, '0112345678', NULL, 'fatima@example.com'),
('خالد', NULL, NULL, '0123456789', NULL),
('سارة', NULL, NULL, NULL, 'sara@example.com');

-- استخدام COALESCE للحصول على أول رقم متاح
SELECT 
    name,
    COALESCE(mobile_phone, home_phone, work_phone, 'لا يوجد رقم') AS رقم_الاتصال,
    COALESCE(email, 'لا يوجد بريد') AS البريد_الإلكتروني
FROM contacts;
الفرق بين IFNULL و COALESCE:
  • IFNULL(a, b) - يقبل معاملين فقط
  • COALESCE(a, b, c, ...) - يقبل عدة معاملات
  • COALESCE متوافق مع معظم قواعد البيانات
  • IFNULL خاص بـ MySQL/MariaDB

دالة NULLIF() - تحويل قيمة معينة إلى NULL

دالة NULLIF() تقارن قيمتين، وإذا كانتا متساويتين تُرجع NULL، وإلا تُرجع القيمة الأولى.

الصيغة الأساسية:

NULLIF(expression1, expression2)

أمثلة عملية:

-- مثال بسيط
SELECT NULLIF(10, 10) AS النتيجة;
-- النتيجة: NULL (لأن القيمتين متساويتين)

SELECT NULLIF(10, 5) AS النتيجة;
-- النتيجة: 10 (لأن القيمتين مختلفتين)

-- مثال عملي: تجنب القسمة على صفر
CREATE TABLE sales_data (
    product_name VARCHAR(100),
    total_sales DECIMAL(10, 2),
    total_orders INT
);

INSERT INTO sales_data VALUES
('منتج أ', 5000.00, 10),
('منتج ب', 3000.00, 0),
('منتج ج', 7500.00, 15);

-- استخدام NULLIF لتجنب القسمة على صفر
SELECT 
    product_name,
    total_sales,
    total_orders,
    ROUND(total_sales / NULLIF(total_orders, 0), 2) AS متوسط_قيمة_الطلب
FROM sales_data;
النتيجة:
product_name total_sales total_orders متوسط_قيمة_الطلب
منتج أ 5000.00 10 500.00
منتج ب 3000.00 0 NULL
منتج ج 7500.00 15 500.00

دمج NULLIF مع COALESCE:

-- القسمة الآمنة مع قيمة افتراضية
SELECT 
    product_name,
    total_sales,
    total_orders,
    COALESCE(
        ROUND(total_sales / NULLIF(total_orders, 0), 2),
        0
    ) AS متوسط_قيمة_الطلب
FROM sales_data;

أمثلة عملية متقدمة

مثال 1: نظام تقييم الأداء

CREATE TABLE employee_performance (
    employee_name VARCHAR(100),
    sales_target DECIMAL(10, 2),
    actual_sales DECIMAL(10, 2),
    customer_rating DECIMAL(3, 2),
    attendance_percentage DECIMAL(5, 2)
);

INSERT INTO employee_performance VALUES
('أحمد', 50000, 65000, 4.5, 95.0),
('فاطمة', 60000, 58000, 4.8, 98.0),
('خالد', 45000, 30000, 3.5, 85.0),
('سارة', 55000, 70000, 4.9, 100.0);

-- تقييم شامل للأداء
SELECT 
    employee_name,
    actual_sales,
    sales_target,
    ROUND((actual_sales / sales_target) * 100, 2) AS نسبة_تحقيق_الهدف,
    customer_rating,
    attendance_percentage,
    -- تقييم المبيعات
    CASE 
        WHEN actual_sales >= sales_target * 1.2 THEN 'ممتاز'
        WHEN actual_sales >= sales_target THEN 'جيد جداً'
        WHEN actual_sales >= sales_target * 0.8 THEN 'جيد'
        WHEN actual_sales >= sales_target * 0.6 THEN 'مقبول'
        ELSE 'ضعيف'
    END AS تقييم_المبيعات,
    -- تقييم رضا العملاء
    CASE 
        WHEN customer_rating >= 4.5 THEN 'ممتاز'
        WHEN customer_rating >= 4.0 THEN 'جيد جداً'
        WHEN customer_rating >= 3.5 THEN 'جيد'
        ELSE 'يحتاج تحسين'
    END AS تقييم_رضا_العملاء,
    -- تقييم الحضور
    IF(attendance_percentage >= 95, 'ممتاز', 
        IF(attendance_percentage >= 90, 'جيد', 'يحتاج تحسين')
    ) AS تقييم_الحضور,
    -- التقييم النهائي
    CASE 
        WHEN actual_sales >= sales_target 
            AND customer_rating >= 4.5 
            AND attendance_percentage >= 95 
        THEN 'موظف متميز'
        WHEN actual_sales >= sales_target * 0.8 
            AND customer_rating >= 4.0 
            AND attendance_percentage >= 90 
        THEN 'موظف جيد'
        ELSE 'يحتاج تطوير'
    END AS التقييم_النهائي
FROM employee_performance;

مثال 2: نظام الخصومات الذكي

CREATE TABLE customer_orders (
    customer_name VARCHAR(100),
    order_amount DECIMAL(10, 2),
    is_vip BOOLEAN,
    previous_orders_count INT,
    payment_method VARCHAR(20)
);

INSERT INTO customer_orders VALUES
('أحمد', 1500.00, TRUE, 25, 'credit_card'),
('فاطمة', 800.00, FALSE, 5, 'cash'),
('خالد', 3000.00, TRUE, 50, 'credit_card'),
('سارة', 500.00, FALSE, 2, 'cash');

-- حساب الخصم بناءً على عوامل متعددة
SELECT 
    customer_name,
    order_amount,
    is_vip,
    previous_orders_count,
    payment_method,
    -- حساب نسبة الخصم
    CASE 
        -- عملاء VIP
        WHEN is_vip = TRUE THEN
            CASE 
                WHEN order_amount >= 2000 THEN 25
                WHEN order_amount >= 1000 THEN 20
                ELSE 15
            END
        -- عملاء عاديين
        ELSE
            CASE 
                WHEN previous_orders_count >= 10 THEN 15
                WHEN previous_orders_count >= 5 THEN 10
                WHEN order_amount >= 1000 THEN 10
                ELSE 5
            END
    END +
    -- خصم إضافي للدفع الإلكتروني
    IF(payment_method = 'credit_card', 2, 0) AS نسبة_الخصم_الإجمالية,
    -- حساب قيمة الخصم
    ROUND(
        order_amount * (
            (CASE 
                WHEN is_vip = TRUE THEN
                    CASE 
                        WHEN order_amount >= 2000 THEN 25
                        WHEN order_amount >= 1000 THEN 20
                        ELSE 15
                    END
                ELSE
                    CASE 
                        WHEN previous_orders_count >= 10 THEN 15
                        WHEN previous_orders_count >= 5 THEN 10
                        WHEN order_amount >= 1000 THEN 10
                        ELSE 5
                    END
            END +
            IF(payment_method = 'credit_card', 2, 0)) / 100
        ),
        2
    ) AS قيمة_الخصم,
    -- المبلغ النهائي
    ROUND(
        order_amount - (order_amount * (
            (CASE 
                WHEN is_vip = TRUE THEN
                    CASE 
                        WHEN order_amount >= 2000 THEN 25
                        WHEN order_amount >= 1000 THEN 20
                        ELSE 15
                    END
                ELSE
                    CASE 
                        WHEN previous_orders_count >= 10 THEN 15
                        WHEN previous_orders_count >= 5 THEN 10
                        WHEN order_amount >= 1000 THEN 10
                        ELSE 5
                    END
            END +
            IF(payment_method = 'credit_card', 2, 0)) / 100
        )),
        2
    ) AS المبلغ_النهائي
FROM customer_orders;

مثال 3: تقرير شامل للمنتجات

-- تقرير تحليلي شامل
SELECT 
    product_name,
    price,
    stock_quantity,
    -- الفئة السعرية
    CASE 
        WHEN price >= 2000 THEN 'فاخر'
        WHEN price >= 500 THEN 'متوسط'
        ELSE 'اقتصادي'
    END AS الفئة_السعرية,
    -- حالة المخزون
    CASE 
        WHEN stock_quantity = 0 THEN 'نفذ'
        WHEN stock_quantity < 20 THEN 'منخفض'
        WHEN stock_quantity < 50 THEN 'متوسط'
        ELSE 'جيد'
    END AS حالة_المخزون,
    -- قيمة المخزون
    ROUND(price * stock_quantity, 2) AS قيمة_المخزون,
    -- الأولوية
    CASE 
        WHEN stock_quantity = 0 AND price > 1000 THEN 'عاجل جداً'
        WHEN stock_quantity < 10 AND price > 1000 THEN 'عاجل'
        WHEN stock_quantity < 20 THEN 'متوسط'
        ELSE 'عادي'
    END AS أولوية_إعادة_الطلب,
    -- الكمية المقترحة للطلب
    CASE 
        WHEN stock_quantity = 0 THEN 100
        WHEN stock_quantity < 20 THEN 50
        WHEN stock_quantity < 50 THEN 30
        ELSE 0
    END AS الكمية_المقترحة_للطلب
FROM products
ORDER BY 
    CASE 
        WHEN stock_quantity = 0 AND price > 1000 THEN 1
        WHEN stock_quantity < 10 AND price > 1000 THEN 2
        WHEN stock_quantity < 20 THEN 3
        ELSE 4
    END,
    price DESC;

أخطاء شائعة وكيفية تجنبها

1. نسيان ELSE في CASE

خطأ شائع: عدم تحديد قيمة افتراضية

-- قد يُرجع NULL للقيم غير المتوقعة
SELECT 
    CASE 
        WHEN price > 1000 THEN 'غالي'
        WHEN price > 500 THEN 'متوسط'
    END AS الفئة
FROM products;

-- الأفضل: إضافة ELSE
SELECT 
    CASE 
        WHEN price > 1000 THEN 'غالي'
        WHEN price > 500 THEN 'متوسط'
        ELSE 'رخيص'
    END AS الفئة
FROM products;

2. الخلط بين IFNULL و NULLIF

تذكر:

  • IFNULL(a, b) - إذا كان a = NULL، أرجع b
  • NULLIF(a, b) - إذا كان a = b، أرجع NULL

3. عدم مراعاة ترتيب الشروط في CASE

خطأ شائع: ترتيب خاطئ للشروط

-- خطأ: الشرط الأول يلتقط كل القيم
SELECT 
    CASE 
        WHEN price > 0 THEN 'له سعر'  -- هذا سيتحقق دائماً!
        WHEN price > 1000 THEN 'غالي'
        WHEN price > 500 THEN 'متوسط'
    END AS الفئة
FROM products;

-- الصحيح: ابدأ بالشروط الأكثر تحديداً
SELECT 
    CASE 
        WHEN price > 1000 THEN 'غالي'
        WHEN price > 500 THEN 'متوسط'
        WHEN price > 0 THEN 'رخيص'
        ELSE 'مجاني'
    END AS الفئة
FROM products;

أفضل الممارسات

1. استخدم CASE للشروط المتعددة و IF للشروط البسيطة

-- للشروط البسيطة: استخدم IF
SELECT IF(stock_quantity > 0, 'متوفر', 'غير متوفر') AS الحالة;

-- للشروط المتعددة: استخدم CASE
SELECT 
    CASE 
        WHEN stock_quantity = 0 THEN 'نفذ'
        WHEN stock_quantity < 20 THEN 'منخفض'
        WHEN stock_quantity < 50 THEN 'متوسط'
        ELSE 'جيد'
    END AS الحالة;

2. استخدم COALESCE بدلاً من IFNULL للتوافق

-- COALESCE متوافق مع جميع قواعد البيانات
SELECT COALESCE(bonus, 0) AS المكافأة;

-- بدلاً من IFNULL (خاص بـ MySQL)
SELECT IFNULL(bonus, 0) AS المكافأة;

3. أضف تعليقات للشروط المعقدة

SELECT 
    product_name,
    -- تصنيف بناءً على السعر والمخزون
    CASE 
        -- منتجات فاخرة
        WHEN price > 2000 THEN 'فاخر'
        -- منتجات متوسطة
        WHEN price > 500 THEN 'متوسط'
        -- منتجات اقتصادية
        ELSE 'اقتصادي'
    END AS الفئة
FROM products;

4. تجنب التكرار باستخدام CTE أو Subqueries

-- بدلاً من تكرار نفس CASE عدة مرات
WITH categorized_products AS (
    SELECT 
        product_name,
        price,
        CASE 
            WHEN price > 2000 THEN 'فاخر'
            WHEN price > 500 THEN 'متوسط'
            ELSE 'اقتصادي'
        END AS الفئة
    FROM products
)
SELECT 
    الفئة,
    COUNT(*) AS العدد,
    AVG(price) AS متوسط_السعر
FROM categorized_products
GROUP BY الفئة;

ملخص الدرس

في هذا الدرس، تعلمنا كيفية استخدام الدوال الشرطية في SQL:

  • CASE WHEN - للمنطق الشرطي المتقدم والمتعدد المستويات
  • IF() - للشروط البسيطة (MySQL/MariaDB)
  • IFNULL() - لاستبدال NULL بقيمة افتراضية
  • COALESCE() - لاختيار أول قيمة غير NULL من عدة خيارات
  • NULLIF() - لتحويل قيمة معينة إلى NULL

هذه الدوال تمنحك مرونة هائلة في معالجة البيانات وتصنيفها، وهي ضرورية لإنشاء تقارير ذكية واستعلامات متقدمة.

في الدرس القادم: سنبدأ في تعلم الربط بين الجداول (Joins) في SQL، وهو من أهم المواضيع التي ستمكنك من دمج البيانات من جداول متعددة وإنشاء استعلامات معقدة وقوية.

الخطوة التالية: دوال النوافذ (Window Functions)

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

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

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

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

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

انضم الآن