ما هي الاستعلامات الفرعية؟
الاستعلام الفرعي (Subquery) هو استعلام SELECT داخل استعلام آخر. يُستخدم للحصول على بيانات تُستخدم في الاستعلام الخارجي.
-- الشكل العام
SELECT column
FROM table
WHERE column = (SELECT column FROM table WHERE condition);
أنواع الاستعلامات الفرعية
1. Subquery في WHERE
إرجاع قيمة واحدة (Scalar Subquery)
-- الموظفون الذين راتبهم أعلى من المتوسط
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- الموظف الأعلى راتباً
SELECT name, salary
FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees);
إرجاع قائمة (مع IN)
-- الموظفون في الأقسام الموجودة في الرياض
SELECT name, department_id
FROM employees
WHERE department_id IN (
SELECT id
FROM departments
WHERE location = 'الرياض'
);
-- الموظفون الذين طلبوا منتجات
SELECT DISTINCT customer_name
FROM orders
WHERE product_id IN (
SELECT id
FROM products
WHERE category = 'إلكترونيات'
);
NOT IN
-- الموظفون الذين ليسوا في قسم التقنية أو المبيعات
SELECT name
FROM employees
WHERE department_id NOT IN (
SELECT id
FROM departments
WHERE name IN ('التقنية', 'المبيعات')
);
2. Subquery في SELECT
-- كل موظف مع متوسط راتب قسمه
SELECT name,
salary,
department_id,
(SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id) AS dept_avg
FROM employees e1;
-- كل قسم مع عدد موظفيه
SELECT name,
(SELECT COUNT(*)
FROM employees
WHERE department_id = d.id) AS employee_count
FROM departments d;
3. Subquery في FROM (Derived Table)
-- معالجة البيانات على مرحلتين
SELECT department, avg_salary
FROM (
SELECT department_id AS department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
) AS dept_stats
WHERE avg_salary > 5000;
-- أعلى راتب في كل قسم
SELECT dept_salaries.department_id, dept_salaries.max_salary
FROM (
SELECT department_id,
MAX(salary) AS max_salary
FROM employees
GROUP BY department_id
) AS dept_salaries;
EXISTS و NOT EXISTS
EXISTS يتحقق من وجود صفوف في الاستعلام الفرعي.
-- الأقسام التي فيها موظفون
SELECT name
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.id
);
-- الأقسام الفارغة (بدون موظفين)
SELECT name
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.id
);
-- المنتجات التي لم تُطلب أبداً
SELECT name
FROM products p
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.product_id = p.id
);
EXISTS vs IN
EXISTS: أسرع عندما الجدول الخارجي صغير والداخلي كبير
IN: أسرع عندما الاستعلام الفرعي يُرجع قيماً قليلة
ALL و ANY
ALL - مقارنة مع جميع القيم
-- الموظفون الذين راتبهم أعلى من كل موظفي المبيعات
SELECT name, salary
FROM employees
WHERE salary > ALL (
SELECT salary
FROM employees
WHERE department_id = 1
);
-- يجب أن يكون الراتب أعلى من الجميع
ANY (أو SOME) - مقارنة مع أي قيمة
-- الموظفون الذين راتبهم أعلى من أي موظف في المبيعات
SELECT name, salary
FROM employees
WHERE salary > ANY (
SELECT salary
FROM employees
WHERE department_id = 1
);
-- يكفي أن يكون أعلى من واحد فقط
Correlated Subqueries (المترابطة)
الاستعلام الفرعي يعتمد على الاستعلام الخارجي.
-- الموظفون الذين راتبهم أعلى من متوسط قسمهم
SELECT name, salary, department_id
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);
-- آخر طلب لكل عميل
SELECT *
FROM orders o1
WHERE order_date = (
SELECT MAX(order_date)
FROM orders o2
WHERE o2.customer_name = o1.customer_name
);
أمثلة عملية متقدمة
المنتج الأكثر مبيعاً
SELECT name
FROM products
WHERE id = (
SELECT product_id
FROM orders
GROUP BY product_id
ORDER BY SUM(quantity) DESC
LIMIT 1
);
الموظف الثاني في الراتب
SELECT name, salary
FROM employees
WHERE salary = (
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1
);
نسبة راتب كل موظف من الإجمالي
SELECT name,
salary,
ROUND(salary * 100.0 / (SELECT SUM(salary) FROM employees), 2) AS percentage
FROM employees
ORDER BY percentage DESC;
الأقسام مع أكثر من المتوسط العام للموظفين
SELECT d.name, COUNT(e.id) AS emp_count
FROM departments d
JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
HAVING COUNT(e.id) > (
SELECT AVG(emp_count)
FROM (
SELECT COUNT(*) AS emp_count
FROM employees
GROUP BY department_id
) AS counts
);
Subquery vs JOIN
-- نفس النتيجة بطريقتين:
-- باستخدام Subquery:
SELECT name FROM employees
WHERE department_id IN (
SELECT id FROM departments WHERE location = 'الرياض'
);
-- باستخدام JOIN:
SELECT e.name
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.location = 'الرياض';
متى نستخدم كل واحد؟
- Subquery: عندما تحتاج قيمة واحدة أو قائمة للمقارنة
- JOIN: عندما تحتاج أعمدة من كلا الجدولين في النتيجة
- JOIN غالباً أسرع، لكن Subquery أوضح أحياناً
تمارين للتطبيق
- اعرض الموظفين الذين راتبهم أعلى من متوسط الشركة
- اعرض المنتجات التي سعرها أعلى من متوسط الأسعار
- اعرض الأقسام التي ليس فيها موظفون
- اعرض العميل الذي لديه أكثر عدد من الطلبات
الخلاصة
- الاستعلامات الفرعية = استعلام داخل استعلام
- تُستخدم في
WHERE,SELECT,FROM INللقوائم،EXISTSللوجودALLللمقارنة مع الكل،ANYللمقارنة مع واحد- الاستعلامات المترابطة تعتمد على الخارجي
تهانينا!
أكملت جميع دروس الدورة! الآن لديك أساس قوي في SQL. استمر في التدريب باستخدام صفحة التجربة وطبّق ما تعلمته على مشاريع حقيقية.