الاستعلامات الفرعية في FROM

الاستعلامات الفرعية في جملة FROM

استخدام الاستعلامات الفرعية في جملة FROM يسمح لك بإنشاء جداول مشتقة (Derived Tables) أو جداول مؤقتة (Inline Views). هذه التقنية قوية جداً لتبسيط الاستعلامات المعقدة وتنظيم المنطق.

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

الاستخدامات الشائعة:

  • تبسيط الاستعلامات المعقدة بتقسيمها إلى خطوات
  • إجراء حسابات على نتائج مجمعة (GROUP BY)
  • إنشاء تقارير متعددة المستويات
  • تجنب تكرار الاستعلامات الفرعية المعقدة
  • تحسين قابلية القراءة والصيانة

قاعدة إلزامية: يجب دائماً إعطاء الاستعلام الفرعي في FROM اسماً مستعاراً (alias)، وإلا سيحدث خطأ.

بيانات الأمثلة

سنستخدم نفس الجداول من الدرس السابق:

CREATE TABLE employees (
    emp_id INT PRIMARY KEY,
    emp_name VARCHAR(100),
    department VARCHAR(50),
    salary DECIMAL(10, 2),
    manager_id INT,
    hire_date DATE
);

CREATE TABLE departments (
    dept_id INT PRIMARY KEY,
    dept_name VARCHAR(50),
    location VARCHAR(50),
    budget DECIMAL(12, 2)
);

CREATE TABLE sales (
    sale_id INT PRIMARY KEY,
    emp_id INT,
    sale_date DATE,
    amount DECIMAL(10, 2),
    region VARCHAR(50)
);

-- إدراج بيانات الموظفين
INSERT INTO employees VALUES
(1, 'أحمد محمد', 'تطوير', 15000, NULL, '2018-01-15'),
(2, 'فاطمة علي', 'تطوير', 12000, 1, '2019-03-20'),
(3, 'خالد حسن', 'تسويق', 9000, NULL, '2020-06-10'),
(4, 'سارة أحمد', 'تسويق', 11000, 3, '2019-08-05'),
(5, 'محمد عبدالله', 'مبيعات', 8000, NULL, '2021-01-12'),
(6, 'نورا خالد', 'مبيعات', 8500, 5, '2020-11-20'),
(7, 'علي حسين', 'تطوير', 13000, 1, '2018-05-10'),
(8, 'ليلى سعيد', 'موارد بشرية', 10000, NULL, '2019-09-15');

-- إدراج بيانات الأقسام
INSERT INTO departments VALUES
(1, 'تطوير', 'الرياض', 500000),
(2, 'تسويق', 'جدة', 300000),
(3, 'مبيعات', 'الدمام', 250000),
(4, 'موارد بشرية', 'الرياض', 150000);

-- إدراج بيانات المبيعات
INSERT INTO sales VALUES
(1, 5, '2024-01-15', 5000, 'الشرق'),
(2, 5, '2024-01-20', 7500, 'الشرق'),
(3, 6, '2024-01-18', 6000, 'الشرق'),
(4, 5, '2024-02-10', 8000, 'الغرب'),
(5, 6, '2024-02-15', 5500, 'الغرب');

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

الشكل العام:

SELECT columns
FROM (
    SELECT columns
    FROM table
    WHERE conditions
) AS alias_name  -- الـ alias إلزامي!
WHERE conditions;

عناصر أساسية:

  • الأقواس ( ): تحيط بالاستعلام الفرعي
  • AS alias_name: اسم مستعار للجدول المشتق (إلزامي)
  • الأعمدة: يمكنك الوصول فقط للأعمدة المحددة في SELECT الداخلي
  • المعاملة: يُعامل كجدول عادي يمكن الربط معه أو التصفية منه

أمثلة أساسية

مثال 1: حساب متوسط الرواتب لكل قسم ثم التصفية

-- نريد الأقسام التي متوسط رواتبها أعلى من 10000
SELECT department, avg_salary
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_averages
WHERE avg_salary > 10000
ORDER BY avg_salary DESC;
النتيجة:
department avg_salary
تطوير 13333.33
تسويق 10000.00

لماذا نستخدم Derived Table هنا؟

لا يمكنك استخدام WHERE avg_salary > 10000 مباشرة لأن WHERE يُنفذ قبل GROUP BY. الحل هو استخدام جدول مشتق أو HAVING.

مثال 2: إضافة ترتيب (Ranking) للموظفين حسب الراتب

SELECT emp_name, salary, salary_rank
FROM (
    SELECT 
        emp_name,
        salary,
        RANK() OVER (ORDER BY salary DESC) AS salary_rank
    FROM employees
) AS ranked_employees
WHERE salary_rank <= 3;

مثال 3: حساب النسبة المئوية لكل قسم من إجمالي الرواتب

SELECT 
    department,
    dept_total,
    overall_total,
    ROUND((dept_total * 100.0 / overall_total), 2) AS percentage
FROM (
    SELECT 
        department,
        SUM(salary) AS dept_total,
        (SELECT SUM(salary) FROM employees) AS overall_total
    FROM employees
    GROUP BY department
) AS dept_totals
ORDER BY percentage DESC;

ربط الجداول المشتقة

يمكنك ربط جدول مشتق مع جداول عادية أو جداول مشتقة أخرى.

مثال 1: ربط إحصائيات الأقسام مع بيانات الموظفين

SELECT 
    e.emp_name,
    e.salary,
    e.department,
    ds.avg_salary AS متوسط_القسم,
    ds.emp_count AS عدد_الموظفين,
    e.salary - ds.avg_salary AS الفرق_عن_المتوسط
FROM employees e
JOIN (
    SELECT 
        department,
        AVG(salary) AS avg_salary,
        COUNT(*) AS emp_count
    FROM employees
    GROUP BY department
) AS ds ON e.department = ds.department
ORDER BY e.department, e.salary DESC;

مثال 2: ربط جدولين مشتقين

-- مقارنة أداء الموظفين في المبيعات
SELECT 
    emp_stats.emp_name,
    emp_stats.total_sales,
    dept_stats.avg_dept_sales,
    CASE 
        WHEN emp_stats.total_sales > dept_stats.avg_dept_sales 
        THEN 'فوق المتوسط'
        ELSE 'تحت المتوسط'
    END AS الأداء
FROM (
    -- إجمالي مبيعات كل موظف
    SELECT 
        e.emp_id,
        e.emp_name,
        e.department,
        COALESCE(SUM(s.amount), 0) AS total_sales
    FROM employees e
    LEFT JOIN sales s ON e.emp_id = s.emp_id
    GROUP BY e.emp_id, e.emp_name, e.department
) AS emp_stats
JOIN (
    -- متوسط المبيعات لكل قسم
    SELECT 
        e.department,
        AVG(COALESCE(total, 0)) AS avg_dept_sales
    FROM employees e
    LEFT JOIN (
        SELECT emp_id, SUM(amount) AS total
        FROM sales
        GROUP BY emp_id
    ) s ON e.emp_id = s.emp_id
    GROUP BY e.department
) AS dept_stats ON emp_stats.department = dept_stats.department
WHERE emp_stats.total_sales > 0
ORDER BY emp_stats.total_sales DESC;

التجميعات متعددة المستويات

الجداول المشتقة مفيدة جداً عندما تحتاج إلى إجراء تجميعات على نتائج مجمعة بالفعل.

مثال 1: متوسط متوسطات الأقسام

-- حساب متوسط "متوسطات الرواتب" للأقسام
SELECT 
    AVG(avg_salary) AS متوسط_المتوسطات,
    MAX(avg_salary) AS أعلى_متوسط,
    MIN(avg_salary) AS أدنى_متوسط,
    COUNT(*) AS عدد_الأقسام
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_averages;

مثال 2: تصنيف الأقسام حسب إجمالي الرواتب

SELECT 
    department,
    total_salary,
    emp_count,
    CASE 
        WHEN total_salary > 30000 THEN 'قسم كبير'
        WHEN total_salary > 15000 THEN 'قسم متوسط'
        ELSE 'قسم صغير'
    END AS التصنيف
FROM (
    SELECT 
        department,
        SUM(salary) AS total_salary,
        COUNT(*) AS emp_count
    FROM employees
    GROUP BY department
) AS dept_summary
ORDER BY total_salary DESC;

مثال 3: إحصائيات المبيعات الشهرية

SELECT 
    month_name,
    total_sales,
    sale_count,
    avg_sale,
    RANK() OVER (ORDER BY total_sales DESC) AS month_rank
FROM (
    SELECT 
        DATE_FORMAT(sale_date, '%Y-%m') AS month_name,
        SUM(amount) AS total_sales,
        COUNT(*) AS sale_count,
        AVG(amount) AS avg_sale
    FROM sales
    GROUP BY DATE_FORMAT(sale_date, '%Y-%m')
) AS monthly_stats
ORDER BY month_name;

تقارير معقدة

مثال 1: تقرير شامل للموظفين مع مقارنات متعددة

SELECT 
    e.emp_name,
    e.salary,
    e.department,
    stats.avg_salary AS متوسط_الشركة,
    stats.max_salary AS أعلى_راتب,
    dept_stats.avg_dept_salary AS متوسط_القسم,
    e.salary - stats.avg_salary AS الفرق_عن_متوسط_الشركة,
    e.salary - dept_stats.avg_dept_salary AS الفرق_عن_متوسط_القسم,
    ROUND((e.salary * 100.0 / stats.max_salary), 2) AS نسبة_من_الأعلى
FROM employees e
CROSS JOIN (
    -- إحصائيات الشركة
    SELECT 
        AVG(salary) AS avg_salary,
        MAX(salary) AS max_salary,
        MIN(salary) AS min_salary
    FROM employees
) AS stats
JOIN (
    -- إحصائيات الأقسام
    SELECT 
        department,
        AVG(salary) AS avg_dept_salary,
        COUNT(*) AS dept_emp_count
    FROM employees
    GROUP BY department
) AS dept_stats ON e.department = dept_stats.department
ORDER BY e.salary DESC;

مثال 2: تحليل الأداء الإقليمي للمبيعات

SELECT 
    region_stats.region,
    region_stats.total_sales,
    region_stats.sale_count,
    region_stats.avg_sale,
    overall.total_all_sales,
    ROUND((region_stats.total_sales * 100.0 / overall.total_all_sales), 2) AS نسبة_المساهمة
FROM (
    -- إحصائيات كل منطقة
    SELECT 
        region,
        SUM(amount) AS total_sales,
        COUNT(*) AS sale_count,
        AVG(amount) AS avg_sale
    FROM sales
    GROUP BY region
) AS region_stats
CROSS JOIN (
    -- الإجمالي الكلي
    SELECT SUM(amount) AS total_all_sales
    FROM sales
) AS overall
ORDER BY region_stats.total_sales DESC;

مثال 3: تقرير الموظفين الأعلى أداءً

SELECT 
    emp_name,
    department,
    total_sales,
    sale_count,
    avg_sale_amount,
    performance_rank
FROM (
    SELECT 
        e.emp_name,
        e.department,
        COUNT(s.sale_id) AS sale_count,
        COALESCE(SUM(s.amount), 0) AS total_sales,
        COALESCE(AVG(s.amount), 0) AS avg_sale_amount,
        RANK() OVER (ORDER BY COALESCE(SUM(s.amount), 0) DESC) AS performance_rank
    FROM employees e
    LEFT JOIN sales s ON e.emp_id = s.emp_id
    WHERE e.department = 'مبيعات'
    GROUP BY e.emp_id, e.emp_name, e.department
) AS sales_performance
WHERE performance_rank <= 5
ORDER BY performance_rank;

الدمج مع WHERE و HAVING

مثال 1: تصفية قبل وبعد التجميع

-- الأقسام التي متوسط رواتبها > 10000 ولديها أكثر من موظف واحد
SELECT department, avg_salary, emp_count
FROM (
    SELECT 
        department,
        AVG(salary) AS avg_salary,
        COUNT(*) AS emp_count
    FROM employees
    WHERE salary > 5000  -- تصفية قبل التجميع
    GROUP BY department
    HAVING COUNT(*) > 1  -- تصفية بعد التجميع
) AS filtered_depts
WHERE avg_salary > 10000  -- تصفية إضافية على النتيجة
ORDER BY avg_salary DESC;

مثال 2: استعلام متعدد المستويات

-- الموظفين في الأقسام التي متوسط رواتبها في أعلى 50%
SELECT emp_name, salary, department
FROM employees
WHERE department IN (
    SELECT department
    FROM (
        SELECT 
            department,
            AVG(salary) AS avg_salary
        FROM employees
        GROUP BY department
    ) AS dept_avgs
    WHERE avg_salary >= (
        SELECT AVG(avg_salary)
        FROM (
            SELECT department, AVG(salary) AS avg_salary
            FROM employees
            GROUP BY department
        ) AS all_dept_avgs
    )
)
ORDER BY department, salary DESC;

نصائح الأداء

1. استخدم الفهارس بحكمة

تأكد من وجود فهارس على الأعمدة المستخدمة في:

  • شروط WHERE داخل الاستعلام الفرعي
  • أعمدة GROUP BY
  • أعمدة JOIN بين الجداول المشتقة والجداول العادية

2. حدد الأعمدة المطلوبة فقط

-- ❌ سيء: اختيار جميع الأعمدة
SELECT * FROM (
    SELECT * FROM employees
) AS all_employees;

-- ✅ جيد: اختيار الأعمدة المطلوبة فقط
SELECT emp_name, salary FROM (
    SELECT emp_name, salary FROM employees
) AS selected_employees;

3. فكر في استخدام CTEs للاستعلامات المعقدة

للاستعلامات المعقدة جداً، WITH (CTE) قد يكون أكثر وضوحاً:

-- باستخدام Derived Table
SELECT * FROM (
    SELECT department, AVG(salary) AS avg_sal
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_sal > 10000;

-- باستخدام CTE (أكثر وضوحاً)
WITH dept_avg AS (
    SELECT department, AVG(salary) AS avg_sal
    FROM employees
    GROUP BY department
)
SELECT * FROM dept_avg
WHERE avg_sal > 10000;

أخطاء شائعة

1. نسيان الـ Alias

-- ❌ خطأ: بدون alias
SELECT department, avg_salary
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
);
-- Error: Every derived table must have its own alias

-- ✅ صحيح: مع alias
SELECT department, avg_salary
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_averages;

2. محاولة الوصول لأعمدة غير محددة

-- ❌ خطأ: emp_id غير موجود في SELECT الداخلي
SELECT emp_id, avg_salary
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg;
-- Error: Unknown column 'emp_id'

-- ✅ صحيح: استخدم فقط الأعمدة المحددة
SELECT department, avg_salary
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg;

3. تعقيد غير ضروري

-- ❌ معقد بدون داعٍ
SELECT * FROM (
    SELECT * FROM employees
) AS all_emp;

-- ✅ أبسط وأوضح
SELECT * FROM employees;

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

1. استخدم أسماء واضحة ومعبرة للـ Aliases

-- ❌ غير واضح
SELECT * FROM (
    SELECT department, AVG(salary) AS avg
    FROM employees
    GROUP BY department
) AS t1;

-- ✅ واضح ومعبر
SELECT * FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS department_averages;

2. قسّم الاستعلامات المعقدة إلى خطوات منطقية

استخدم الجداول المشتقة لتقسيم المنطق المعقد:

  • الخطوة 1: جمع البيانات الأساسية
  • الخطوة 2: إجراء الحسابات
  • الخطوة 3: التصفية والترتيب

3. أضف تعليقات للاستعلامات المعقدة

SELECT 
    e.emp_name,
    e.salary,
    stats.avg_salary
FROM employees e
JOIN (
    -- حساب متوسط الراتب لكل قسم
    -- يستثني الموظفين الجدد (أقل من 6 أشهر)
    SELECT 
        department,
        AVG(salary) AS avg_salary
    FROM employees
    WHERE hire_date < DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
    GROUP BY department
) AS stats ON e.department = stats.department;

4. اختر بين Derived Tables و CTEs حسب الحالة

استخدم Derived Tables عندما:

  • الاستعلام بسيط ومباشر
  • تحتاج استخدام النتيجة مرة واحدة فقط

استخدم CTEs عندما:

  • الاستعلام معقد ويحتاج توضيح
  • تحتاج استخدام نفس النتيجة عدة مرات
  • تريد استعلامات متداخلة متعددة المستويات

ملخص الدرس

في هذا الدرس، تعلمنا عن الاستعلامات الفرعية في جملة FROM:

  • الجداول المشتقة (Derived Tables) تُنشأ من استعلامات فرعية في FROM
  • يجب دائماً إعطاء الجدول المشتق اسماً مستعاراً (alias)
  • مفيدة للتجميعات متعددة المستويات
  • يمكن ربطها مع جداول عادية أو جداول مشتقة أخرى
  • تبسط الاستعلامات المعقدة بتقسيمها إلى خطوات
  • يمكنك الوصول فقط للأعمدة المحددة في SELECT الداخلي
  • استخدم CTEs للاستعلامات المعقدة جداً
  • انتبه للأداء واستخدم الفهارس المناسبة

حالات الاستخدام الشائعة:

الحالة المثال
تصفية نتائج مجمعة الأقسام بمتوسط راتب > 10000
حسابات متعددة المستويات متوسط المتوسطات
تقارير معقدة مقارنة الموظف بالقسم والشركة
ربط إحصائيات ربط إحصائيات الأقسام بالموظفين
ترتيب وتصنيف أعلى 3 موظفين راتباً

نصيحة ذهبية: إذا وجدت نفسك تكتب استعلامات فرعية معقدة جداً في FROM، فكر في استخدام WITH (CTE) لتحسين الوضوح والقابلية للصيانة.

في الدرس القادم: سنتعلم بالتفصيل عن الاستعلامات الفرعية المترابطة (Correlated Subqueries)، وكيف تعمل، ومتى تستخدمها، وكيفية تحسين أدائها.

الخطوة التالية: الاستعلامات الفرعية المترابطة (Correlated Subqueries)

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

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

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

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

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

انضم الآن