الدرس 34 استعلامات متقدمة

عمليات المجموعات Set Operations

عمليات المجموعات في SQL تسمح بدمج نتائج استعلامين أو أكثر. مستوحاة من نظرية المجموعات في الرياضيات، هذه العمليات تشمل UNION (الاتحاد)، INTERSECT (التقاطع)، و EXCEPT (الفرق).

⏱️ مدة القراءة: 30 دقيقة
📊 المستوى: متوسط
🎯 الوحدة: استعلامات متقدمة

نظرية المجموعات

قبل الغوص في SQL، دعنا نفهم المفاهيم الأساسية من نظرية المجموعات:

📐 العمليات الأساسية
  • UNION (الاتحاد): جميع العناصر في A أو B أو كليهما
  • INTERSECT (التقاطع): العناصر المشتركة بين A و B فقط
  • EXCEPT (الفرق): العناصر في A وليست في B
العملية الوصف مثال (A={1,2,3}, B={2,3,4})
A UNION B كل العناصر {1, 2, 3, 4}
A INTERSECT B العناصر المشتركة {2, 3}
A EXCEPT B في A وليس في B {1}
B EXCEPT A في B وليس في A {4}

عملية UNION (الاتحاد)

UNION تدمج نتائج استعلامين وتزيل التكرارات.

SQL - UNION الأساسي
-- الصيغة الأساسية
SELECT column1, column2 FROM table1
UNION
SELECT column1, column2 FROM table2;

-- مثال: قائمة جميع المدن من جدولين
SELECT city FROM customers
UNION
SELECT city FROM suppliers;

-- النتيجة: قائمة فريدة بجميع المدن (بدون تكرار)

UNION ALL - بدون إزالة التكرار

SQL - UNION ALL
-- UNION ALL يحتفظ بالتكرارات
SELECT city FROM customers
UNION ALL
SELECT city FROM suppliers;

-- الفرق:
-- UNION: يزيل التكرارات (أبطأ)
-- UNION ALL: يحتفظ بالتكرارات (أسرع)

-- متى نستخدم UNION ALL؟
-- 1. عندما نعرف أنه لا توجد تكرارات
-- 2. عندما نريد الاحتفاظ بالتكرارات
-- 3. لتحسين الأداء

أمثلة عملية لـ UNION

SQL - سيناريوهات واقعية
-- 1. دمج جهات الاتصال من مصادر مختلفة
SELECT name, email, 'عميل' AS source FROM customers
UNION
SELECT name, email, 'مورد' AS source FROM suppliers
UNION
SELECT name, email, 'موظف' AS source FROM employees;

-- 2. البحث في جداول متعددة
SELECT id, name, 'منتج' AS type
FROM products WHERE name LIKE '%لابتوب%'
UNION
SELECT id, name, 'خدمة' AS type
FROM services WHERE name LIKE '%لابتوب%';

-- 3. إنشاء قائمة منسدلة موحدة
SELECT 'كل الأقسام' AS department_name, 0 AS department_id
UNION
SELECT department_name, department_id
FROM departments
ORDER BY department_id;

-- 4. تقرير شامل من جداول مختلفة
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    'مبيعات' AS type,
    SUM(amount) AS total
FROM sales
GROUP BY DATE_FORMAT(order_date, '%Y-%m')

UNION ALL

SELECT
    DATE_FORMAT(expense_date, '%Y-%m') AS month,
    'مصروفات' AS type,
    SUM(amount) AS total
FROM expenses
GROUP BY DATE_FORMAT(expense_date, '%Y-%m')

ORDER BY month, type;
⚠️ قواعد UNION
  • يجب أن يكون عدد الأعمدة متساوياً في كل استعلام
  • يجب أن تكون أنواع البيانات متوافقة
  • أسماء الأعمدة تُؤخذ من الاستعلام الأول
  • ORDER BY يُستخدم مرة واحدة في النهاية

عملية INTERSECT (التقاطع)

INTERSECT تُرجع الصفوف الموجودة في كلا الاستعلامين.

SQL - INTERSECT
-- الصيغة الأساسية
SELECT column1 FROM table1
INTERSECT
SELECT column1 FROM table2;

-- مثال: المدن المشتركة بين العملاء والموردين
SELECT city FROM customers
INTERSECT
SELECT city FROM suppliers;

-- العملاء الذين هم أيضاً موظفون
SELECT email FROM customers
INTERSECT
SELECT email FROM employees;

بديل INTERSECT في MySQL

SQL - بديل INTERSECT لـ MySQL
-- MySQL لا يدعم INTERSECT مباشرة (قبل 8.0.31)
-- البديل: استخدام INNER JOIN أو IN

-- باستخدام INNER JOIN
SELECT DISTINCT c.city
FROM customers c
INNER JOIN suppliers s ON c.city = s.city;

-- باستخدام IN
SELECT DISTINCT city FROM customers
WHERE city IN (SELECT city FROM suppliers);

-- باستخدام EXISTS
SELECT DISTINCT city FROM customers c
WHERE EXISTS (
    SELECT 1 FROM suppliers s WHERE s.city = c.city
);

أمثلة عملية لـ INTERSECT

SQL - سيناريوهات واقعية
-- 1. العملاء الذين اشتروا في يناير ويناير
SELECT customer_id FROM orders
WHERE MONTH(order_date) = 1 AND YEAR(order_date) = 2025
INTERSECT
SELECT customer_id FROM orders
WHERE MONTH(order_date) = 2 AND YEAR(order_date) = 2025;

-- 2. المنتجات المتوفرة في جميع المستودعات
SELECT product_id FROM warehouse_a
INTERSECT
SELECT product_id FROM warehouse_b
INTERSECT
SELECT product_id FROM warehouse_c;

-- 3. المهارات المشتركة بين فريقين
SELECT skill FROM team_a_skills
INTERSECT
SELECT skill FROM team_b_skills;

عملية EXCEPT (الفرق)

EXCEPT (أو MINUS في Oracle) تُرجع الصفوف من الاستعلام الأول غير الموجودة في الثاني.

SQL - EXCEPT
-- الصيغة الأساسية
SELECT column1 FROM table1
EXCEPT
SELECT column1 FROM table2;

-- مثال: مدن العملاء غير الموجودة في الموردين
SELECT city FROM customers
EXCEPT
SELECT city FROM suppliers;

-- Oracle: استخدم MINUS بدلاً من EXCEPT
SELECT city FROM customers
MINUS
SELECT city FROM suppliers;

بديل EXCEPT في MySQL

SQL - بديل EXCEPT لـ MySQL
-- باستخدام LEFT JOIN
SELECT DISTINCT c.city
FROM customers c
LEFT JOIN suppliers s ON c.city = s.city
WHERE s.city IS NULL;

-- باستخدام NOT IN
SELECT DISTINCT city FROM customers
WHERE city NOT IN (SELECT city FROM suppliers);

-- باستخدام NOT EXISTS
SELECT DISTINCT city FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM suppliers s WHERE s.city = c.city
);

أمثلة عملية لـ EXCEPT

SQL - سيناريوهات واقعية
-- 1. العملاء الذين لم يشتروا هذا الشهر
SELECT customer_id, customer_name FROM customers
EXCEPT
SELECT DISTINCT c.customer_id, c.customer_name
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE MONTH(o.order_date) = MONTH(CURDATE());

-- 2. المنتجات غير المباعة
SELECT product_id, product_name FROM products
EXCEPT
SELECT DISTINCT p.product_id, p.product_name
FROM products p
JOIN order_items oi ON p.product_id = oi.product_id;

-- 3. الموظفون بدون مشاريع
SELECT employee_id, name FROM employees
EXCEPT
SELECT DISTINCT e.employee_id, e.name
FROM employees e
JOIN project_assignments pa ON e.employee_id = pa.employee_id;

-- 4. المستخدمون الجدد (لم يسجلوا دخول من قبل)
SELECT user_id FROM users
EXCEPT
SELECT DISTINCT user_id FROM login_history;

دمج العمليات

يمكن دمج عمليات المجموعات المتعددة في استعلام واحد.

SQL - عمليات متعددة
-- ترتيب التنفيذ: من الأعلى للأسفل
SELECT city FROM customers
UNION
SELECT city FROM suppliers
EXCEPT
SELECT city FROM excluded_cities;

-- استخدام الأقواس للتحكم في الترتيب
(SELECT city FROM customers
 UNION
 SELECT city FROM suppliers)
EXCEPT
SELECT city FROM excluded_cities;

-- مثال معقد: تحليل العملاء
-- العملاء VIP الذين لم يشتروا مؤخراً
(SELECT customer_id FROM vip_customers
 INTERSECT
 SELECT customer_id FROM customers WHERE total_purchases > 10000)
EXCEPT
SELECT DISTINCT customer_id FROM orders
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY);

القواعد وأفضل الممارسات

📋 قواعد عمليات المجموعات
  1. تطابق الأعمدة: نفس العدد ونفس الترتيب
  2. توافق الأنواع: الأعمدة المقابلة يجب أن تكون متوافقة
  3. الترتيب: ORDER BY في النهاية فقط
  4. الأسماء: تُؤخذ من الاستعلام الأول
SQL - التعامل مع عدم التطابق
-- ❌ خطأ: عدد أعمدة مختلف
SELECT id, name, email FROM customers
UNION
SELECT id, name FROM suppliers;

-- ✅ صحيح: إضافة NULL للأعمدة المفقودة
SELECT id, name, email FROM customers
UNION
SELECT id, name, NULL AS email FROM suppliers;

-- ❌ خطأ: أنواع غير متوافقة
SELECT id, name FROM customers
UNION
SELECT name, id FROM suppliers;  -- name و id مقلوبان

-- ✅ صحيح: ترتيب صحيح
SELECT id, name FROM customers
UNION
SELECT id, name FROM suppliers;

-- استخدام CAST للتوافق
SELECT id, CAST(name AS CHAR(100)) AS name FROM customers
UNION
SELECT id, CAST(name AS CHAR(100)) AS name FROM suppliers;
💡 نصائح الأداء
  • استخدم UNION ALL بدلاً من UNION عندما لا تحتاج لإزالة التكرار
  • ضع الفلاتر (WHERE) في كل استعلام فرعي
  • تجنب ORDER BY في الاستعلامات الفرعية
  • فكر في استخدام JOIN بدلاً من INTERSECT للأداء

مقارنة بين قواعد البيانات

العملية MySQL PostgreSQL SQL Server Oracle
UNION
UNION ALL
INTERSECT ✅ (8.0.31+)
INTERSECT ALL ✅ (8.0.31+)
EXCEPT ✅ (8.0.31+) MINUS
EXCEPT ALL ✅ (8.0.31+)

أمثلة من العالم الحقيقي

1. نظام إدارة المستخدمين

SQL - إدارة الصلاحيات
-- المستخدمون الذين لديهم صلاحية القراءة أو الكتابة
SELECT user_id, 'قراءة' AS permission FROM read_permissions
UNION
SELECT user_id, 'كتابة' AS permission FROM write_permissions;

-- المستخدمون الذين لديهم كلتا الصلاحيتين
SELECT user_id FROM read_permissions
INTERSECT
SELECT user_id FROM write_permissions;

-- المستخدمون الذين لديهم قراءة فقط (بدون كتابة)
SELECT user_id FROM read_permissions
EXCEPT
SELECT user_id FROM write_permissions;

2. نظام التوصيات

SQL - توصيات المنتجات
-- منتجات قد تعجب المستخدم
-- (اشتراها مستخدمون مشابهون ولم يشتريها هذا المستخدم)

-- الخطوة 1: منتجات اشتراها مستخدمون مشابهون
WITH similar_users_products AS (
    SELECT DISTINCT product_id
    FROM purchases
    WHERE user_id IN (
        -- المستخدمون الذين اشتروا نفس المنتجات
        SELECT DISTINCT p2.user_id
        FROM purchases p1
        JOIN purchases p2 ON p1.product_id = p2.product_id
        WHERE p1.user_id = 123 AND p2.user_id != 123
    )
),
-- الخطوة 2: منتجات اشتراها المستخدم الحالي
user_products AS (
    SELECT product_id FROM purchases WHERE user_id = 123
)
-- الخطوة 3: الفرق
SELECT product_id FROM similar_users_products
EXCEPT
SELECT product_id FROM user_products;

3. تحليل الاشتراكات

SQL - تقرير الاشتراكات
-- المشتركون في الخطة الأساسية والمتقدمة معاً
SELECT user_id FROM basic_plan_subscribers
INTERSECT
SELECT user_id FROM advanced_plan_subscribers;

-- المشتركون الذين ألغوا اشتراكهم
SELECT user_id, email, 'ملغي' AS status
FROM (
    SELECT user_id, email FROM all_subscribers
    EXCEPT
    SELECT user_id, email FROM active_subscribers
) cancelled;

-- تقرير شامل للحالات
SELECT user_id, 'نشط في الأساسية فقط' AS status FROM basic_plan_subscribers
EXCEPT SELECT user_id, status FROM advanced_plan_subscribers
UNION ALL
SELECT user_id, 'نشط في المتقدمة فقط' AS status FROM advanced_plan_subscribers
EXCEPT SELECT user_id, status FROM basic_plan_subscribers
UNION ALL
SELECT user_id, 'نشط في كليهما' AS status FROM (
    SELECT user_id FROM basic_plan_subscribers
    INTERSECT
    SELECT user_id FROM advanced_plan_subscribers
) both_plans;

اختبر معلوماتك

السؤال 1: ما الفرق بين UNION و UNION ALL؟

السؤال 2: ماذا تُرجع INTERSECT؟

السؤال 3: ما البديل لـ EXCEPT في Oracle؟