الدوال الشرطية (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;
دالتا 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(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) وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي