الاستعلامات الفرعية في WHERE
الاستعلامات الفرعية في جملة WHERE
استخدام الاستعلامات الفرعية في جملة WHERE هو الاستخدام الأكثر شيوعاً. يسمح لك بتصفية البيانات بناءً على نتائج استعلام آخر، مما يجعل الاستعلامات أكثر ديناميكية وقوة.
في هذا الدرس، سنتعلم كيفية استخدام الاستعلامات الفرعية مع معاملات مختلفة مثل =, IN, EXISTS, ANY, و ALL.
الاستخدامات الشائعة:
- المقارنة مع قيمة محسوبة (متوسط، أقصى، أدنى)
- التصفية بناءً على قائمة قيم من جدول آخر
- التحقق من وجود أو عدم وجود بيانات مرتبطة
- المقارنات المعقدة مع مجموعة من القيم
بيانات الأمثلة
سنستخدم هذه الجداول في جميع الأمثلة:
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 projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100),
dept_name VARCHAR(50),
budget DECIMAL(10, 2),
status VARCHAR(20)
);
-- إدراج بيانات الموظفين
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 projects VALUES
(101, 'تطبيق الجوال', 'تطوير', 80000, 'نشط'),
(102, 'موقع الشركة', 'تطوير', 50000, 'مكتمل'),
(103, 'حملة إعلانية', 'تسويق', 40000, 'نشط'),
(104, 'نظام CRM', 'تطوير', 100000, 'قيد التطوير');
1. المقارنة مع قيمة واحدة (Scalar Subquery)
عندما يُرجع الاستعلام الفرعي قيمة واحدة فقط، يمكن استخدام معاملات المقارنة العادية: =, >, <, >=, <=, !=.
مثال 1: الموظفين برواتب أعلى من المتوسط
SELECT emp_name, salary, department
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
)
ORDER BY salary DESC;
| emp_name | salary | department |
|---|---|---|
| أحمد محمد | 15000.00 | تطوير |
| علي حسين | 13000.00 | تطوير |
| فاطمة علي | 12000.00 | تطوير |
| سارة أحمد | 11000.00 | تسويق |
مثال 2: الموظف صاحب أعلى راتب
SELECT emp_name, salary, department
FROM employees
WHERE salary = (
SELECT MAX(salary)
FROM employees
);
مثال 3: الموظفين المعينين قبل أول موظف في التسويق
SELECT emp_name, hire_date, department
FROM employees
WHERE hire_date < (
SELECT MIN(hire_date)
FROM employees
WHERE department = 'تسويق'
)
ORDER BY hire_date;
2. استخدام IN و NOT IN
عندما يُرجع الاستعلام الفرعي عدة قيم (عمود واحد، صفوف متعددة)، نستخدم IN أو NOT IN.
مثال 1: الموظفين في الأقسام الموجودة في الرياض
SELECT emp_name, department, salary
FROM employees
WHERE department IN (
SELECT dept_name
FROM departments
WHERE location = 'الرياض'
);
| emp_name | department | salary |
|---|---|---|
| أحمد محمد | تطوير | 15000.00 |
| فاطمة علي | تطوير | 12000.00 |
| علي حسين | تطوير | 13000.00 |
| ليلى سعيد | موارد بشرية | 10000.00 |
مثال 2: الموظفين في الأقسام التي لديها مشاريع نشطة
SELECT emp_name, department, salary
FROM employees
WHERE department IN (
SELECT dept_name
FROM projects
WHERE status = 'نشط'
)
ORDER BY department, salary DESC;
مثال 3: الأقسام التي ليس لديها موظفين (NOT IN)
SELECT dept_name, location, budget
FROM departments
WHERE dept_name NOT IN (
SELECT DISTINCT department
FROM employees
);
تحذير مع NOT IN: إذا احتوى الاستعلام الفرعي على قيم NULL، فقد لا تحصل على النتيجة المتوقعة. في هذه الحالة، استخدم NOT EXISTS بدلاً منه.
مثال 4: الموظفين الذين ليسوا مدراء
SELECT emp_name, department, salary
FROM employees
WHERE emp_id NOT IN (
SELECT manager_id
FROM employees
WHERE manager_id IS NOT NULL -- مهم لتجنب مشاكل NULL
);
3. استخدام EXISTS و NOT EXISTS
EXISTS يتحقق من وجود صفوف في نتيجة الاستعلام الفرعي. يُرجع TRUE إذا كان الاستعلام الفرعي يُرجع صفاً واحداً على الأقل، و FALSE إذا لم يُرجع أي صفوف.
مزايا EXISTS:
- أسرع من IN في كثير من الحالات (يتوقف عند أول تطابق)
- لا يتأثر بقيم NULL
- يمكن استخدامه مع Correlated Subqueries بكفاءة
- لا يُرجع بيانات، فقط يتحقق من الوجود
مثال 1: الأقسام التي لديها موظفين
SELECT dept_name, location, budget
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e
WHERE e.department = d.dept_name
);
ملاحظة: في EXISTS، لا يهم ما تختاره في SELECT (يمكن أن يكون 1, *, أو أي شيء). المهم هو وجود صفوف أم لا.
مثال 2: الأقسام التي ليس لديها موظفين (NOT EXISTS)
SELECT dept_name, location, budget
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.department = d.dept_name
);
مثال 3: الموظفين الذين لديهم مرؤوسين
SELECT e1.emp_name, e1.department, e1.salary
FROM employees e1
WHERE EXISTS (
SELECT 1
FROM employees e2
WHERE e2.manager_id = e1.emp_id
)
ORDER BY e1.emp_name;
مثال 4: الموظفين الذين ليس لديهم مرؤوسين
SELECT emp_name, department, salary
FROM employees e1
WHERE NOT EXISTS (
SELECT 1
FROM employees e2
WHERE e2.manager_id = e1.emp_id
)
ORDER BY salary DESC;
مثال 5: الأقسام التي لديها مشاريع نشطة
SELECT dept_name, location
FROM departments d
WHERE EXISTS (
SELECT 1
FROM projects p
WHERE p.dept_name = d.dept_name
AND p.status = 'نشط'
);
4. استخدام ANY و ALL
ANY و ALL يُستخدمان للمقارنة مع مجموعة من القيم.
ANY (أو SOME)
يُرجع TRUE إذا كانت المقارنة صحيحة مع أي قيمة من القيم المُرجعة.
-- الموظفين الذين رواتبهم أعلى من أي موظف في التسويق
SELECT emp_name, salary, department
FROM employees
WHERE salary > ANY (
SELECT salary
FROM employees
WHERE department = 'تسويق'
)
AND department != 'تسويق'
ORDER BY salary DESC;
شرح ANY:
- > ANY = أكبر من أصغر قيمة
- < ANY = أصغر من أكبر قيمة
- = ANY = مساوٍ لأي قيمة (مثل IN)
ALL
يُرجع TRUE إذا كانت المقارنة صحيحة مع جميع القيم المُرجعة.
-- الموظفين الذين رواتبهم أعلى من جميع موظفي التسويق
SELECT emp_name, salary, department
FROM employees
WHERE salary > ALL (
SELECT salary
FROM employees
WHERE department = 'تسويق'
)
ORDER BY salary DESC;
| emp_name | salary | department |
|---|---|---|
| أحمد محمد | 15000.00 | تطوير |
| علي حسين | 13000.00 | تطوير |
| فاطمة علي | 12000.00 | تطوير |
شرح ALL:
- > ALL = أكبر من أكبر قيمة
- < ALL = أصغر من أصغر قيمة
- = ALL = مساوٍ لجميع القيم (نادر الاستخدام)
مثال إضافي: المشاريع بميزانية أقل من جميع مشاريع التطوير
SELECT project_name, dept_name, budget
FROM projects
WHERE budget < ALL (
SELECT budget
FROM projects
WHERE dept_name = 'تطوير'
)
AND dept_name != 'تطوير';
أمثلة متقدمة
مثال 1: الموظفين برواتب أعلى من متوسط قسمهم
-- استعلام فرعي مترابط (Correlated Subquery)
SELECT emp_name, department, salary,
(SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department) AS avg_dept_salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department
)
ORDER BY department, salary DESC;
مثال 2: الأقسام التي متوسط رواتبها أعلى من المتوسط العام
SELECT DISTINCT department
FROM employees e1
WHERE (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department
) > (
SELECT AVG(salary)
FROM employees
);
مثال 3: الموظفين في الأقسام ذات الميزانية الأعلى
SELECT emp_name, e.department, salary
FROM employees e
WHERE e.department IN (
SELECT dept_name
FROM departments
WHERE budget > (
SELECT AVG(budget)
FROM departments
)
)
ORDER BY e.department, salary DESC;
مثال 4: الموظفين الذين تم تعيينهم في نفس سنة أي مدير
SELECT emp_name, hire_date, department
FROM employees
WHERE YEAR(hire_date) IN (
SELECT YEAR(hire_date)
FROM employees
WHERE emp_id IN (
SELECT DISTINCT manager_id
FROM employees
WHERE manager_id IS NOT NULL
)
)
ORDER BY hire_date;
مثال 5: الأقسام التي جميع موظفيها يتقاضون أكثر من 8000
SELECT dept_name, location
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.department = d.dept_name
AND e.salary <= 8000
)
AND EXISTS (
SELECT 1
FROM employees e
WHERE e.department = d.dept_name
);
اعتبارات الأداء
1. EXISTS vs IN
EXISTS عادة أسرع من IN لأنه:
- يتوقف عند إيجاد أول تطابق
- لا يُرجع بيانات فعلية، فقط يتحقق من الوجود
- أكثر كفاءة مع الجداول الكبيرة
-- أسرع في معظم الحالات
WHERE EXISTS (SELECT 1 FROM table2 WHERE ...)
-- قد يكون أبطأ
WHERE column IN (SELECT column FROM table2 WHERE ...)
2. Correlated Subqueries
الاستعلامات الفرعية المترابطة قد تكون بطيئة لأنها تُنفذ لكل صف:
- فكر في استخدام JOIN بدلاً منها إذا أمكن
- تأكد من وجود فهارس على أعمدة الربط
- استخدم EXPLAIN لفحص خطة التنفيذ
3. استخدام DISTINCT عند الحاجة
-- قد يُرجع قيم مكررة
WHERE department IN (SELECT department FROM ...)
-- أفضل إذا كانت هناك قيم مكررة
WHERE department IN (SELECT DISTINCT department FROM ...)
أخطاء شائعة
1. استخدام = مع استعلام يُرجع عدة قيم
-- خطأ: قد يُرجع عدة قيم
SELECT * FROM employees
WHERE department = (
SELECT dept_name FROM departments WHERE location = 'الرياض'
);
-- Error: Subquery returns more than 1 row
-- صحيح: استخدم IN
SELECT * FROM employees
WHERE department IN (
SELECT dept_name FROM departments WHERE location = 'الرياض'
);
2. NOT IN مع قيم NULL
-- مشكلة: إذا كان manager_id يحتوي على NULL
SELECT * FROM employees
WHERE emp_id NOT IN (
SELECT manager_id FROM employees
);
-- قد لا يُرجع أي نتائج!
-- الحل: استخدم NOT EXISTS أو أضف WHERE IS NOT NULL
SELECT * FROM employees e1
WHERE NOT EXISTS (
SELECT 1 FROM employees e2
WHERE e2.manager_id = e1.emp_id
);
3. نسيان الربط في Correlated Subquery
-- خطأ: لا يوجد ربط بين الاستعلامين
SELECT emp_name, salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
-- نسينا: WHERE e2.department = e1.department
);
-- صحيح: مع الربط
SELECT emp_name, salary, department
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department
);
أفضل الممارسات
1. اختر المعامل المناسب
- =, >, < للقيم الفردية
- IN لقائمة قيم محددة
- EXISTS للتحقق من الوجود (أسرع)
- ANY/ALL للمقارنات مع مجموعة
2. استخدم EXISTS بدلاً من IN عندما يكون ممكناً
-- جيد
WHERE department IN (SELECT dept_name FROM departments)
-- أفضل (أسرع في معظم الحالات)
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.dept_name = employees.department
)
3. تجنب Correlated Subqueries إذا أمكن
في كثير من الحالات، يمكن استبدالها بـ JOIN:
-- Correlated Subquery (قد يكون بطيئاً)
SELECT emp_name, salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department
);
-- بديل مع JOIN (عادة أسرع)
SELECT e.emp_name, e.salary
FROM employees e
JOIN (
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
) dept_avg ON e.department = dept_avg.department
WHERE e.salary > dept_avg.avg_sal;
4. أضف تعليقات للاستعلامات المعقدة
-- البحث عن الموظفين في الأقسام ذات الميزانية العالية
-- والذين رواتبهم أعلى من متوسط قسمهم
SELECT emp_name, department, salary
FROM employees e
WHERE department IN (
-- الأقسام بميزانية أعلى من المتوسط
SELECT dept_name
FROM departments
WHERE budget > (SELECT AVG(budget) FROM departments)
)
AND salary > (
-- متوسط راتب القسم
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e.department
);
ملخص الدرس
في هذا الدرس، تعلمنا كيفية استخدام الاستعلامات الفرعية في جملة WHERE:
- المقارنة مع قيمة واحدة باستخدام =, >, <, >=, <=
- IN و NOT IN للتصفية بقائمة قيم
- EXISTS و NOT EXISTS للتحقق من الوجود (أسرع)
- ANY و ALL للمقارنة مع مجموعة قيم
- Correlated Subqueries للمقارنات المعتمدة على الصف الحالي
- EXISTS عادة أسرع من IN
- احذر من NOT IN مع قيم NULL
- استخدم EXPLAIN لفحص الأداء
جدول مقارنة سريع:
| المعامل | الاستخدام | مثال |
|---|---|---|
| =, >, < | قيمة واحدة | WHERE salary > (SELECT AVG(salary) ...) |
| IN | قائمة قيم | WHERE dept IN (SELECT dept_name ...) |
| EXISTS | التحقق من الوجود | WHERE EXISTS (SELECT 1 ...) |
| ANY | أي قيمة من المجموعة | WHERE salary > ANY (SELECT ...) |
| ALL | جميع قيم المجموعة | WHERE salary > ALL (SELECT ...) |
نصيحة ذهبية: عند الشك بين IN و EXISTS، اختر EXISTS - فهو عادة أسرع ولا يتأثر بقيم NULL.
في الدرس القادم: سنتعلم عن الاستعلامات الفرعية في FROM (Derived Tables)، وكيفية إنشاء جداول مؤقتة للاستعلامات المعقدة.
الخطوة التالية: الاستعلامات الفرعية في FROM
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الاستعلامات الفرعية في FROM وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي