مقدمة إلى الاستعلامات الفرعية (Subqueries)
ما هي الاستعلامات الفرعية (Subqueries)؟
Subquery (الاستعلام الفرعي) هو استعلام SQL موضوع داخل استعلام آخر. يُسمى الاستعلام الخارجي بـ الاستعلام الرئيسي (Main Query)، بينما يُسمى الاستعلام الداخلي بـ الاستعلام الفرعي (Subquery) أو الاستعلام المتداخل (Nested Query).
الاستعلامات الفرعية تسمح لك بكتابة استعلامات معقدة بطريقة منطقية ومنظمة، حيث يمكنك استخدام نتيجة استعلام كمدخل لاستعلام آخر.
مثال بسيط:
-- البحث عن الموظفين الذين رواتبهم أعلى من المتوسط
SELECT emp_name, salary
FROM employees
WHERE salary > (
SELECT AVG(salary) -- هذا هو الاستعلام الفرعي
FROM employees
);
في هذا المثال، الاستعلام الفرعي (SELECT AVG(salary) FROM employees) يُنفذ أولاً ويُرجع قيمة واحدة (متوسط الرواتب)، ثم يستخدم الاستعلام الرئيسي هذه القيمة للمقارنة.
ملاحظة مهمة: الاستعلام الفرعي دائماً موضوع بين أقواس ( ) ويُنفذ قبل الاستعلام الرئيسي (في معظم الحالات).
لماذا نستخدم الاستعلامات الفرعية؟
الفوائد الرئيسية:
- تبسيط الاستعلامات المعقدة: تقسيم المشكلة إلى خطوات منطقية
- إعادة الاستخدام: استخدام نتيجة استعلام في أماكن متعددة
- الوضوح: جعل الكود أكثر قابلية للقراءة والفهم
- المرونة: إمكانية كتابة استعلامات ديناميكية
- تجنب الجداول المؤقتة: في بعض الحالات بديل أفضل
أمثلة على حالات الاستخدام:
- البحث عن قيم أعلى/أقل من المتوسط
- المقارنة مع نتائج حسابات معقدة
- التصفية بناءً على وجود/عدم وجود بيانات في جدول آخر
- إنشاء جداول مؤقتة للاستعلامات المعقدة
- الحسابات المتداخلة والمشروطة
الصيغة الأساسية
الشكل العام:
SELECT column1, column2
FROM table1
WHERE column1 = (
SELECT column
FROM table2
WHERE condition
);
عناصر الاستعلام الفرعي:
- الأقواس ( ): إلزامية لتحديد الاستعلام الفرعي
- SELECT: يجب أن يبدأ الاستعلام الفرعي بـ SELECT
- الموضع: يمكن وضعه في WHERE, FROM, SELECT, أو HAVING
- النتيجة: قد تكون قيمة واحدة، صف، عمود، أو جدول كامل
أنواع الاستعلامات الفرعية
1. الاستعلام الفرعي القياسي (Scalar Subquery)
يُرجع قيمة واحدة فقط (صف واحد، عمود واحد).
-- مثال: البحث عن الموظف صاحب أعلى راتب
SELECT emp_name, salary
FROM employees
WHERE salary = (
SELECT MAX(salary)
FROM employees
);
2. الاستعلام الفرعي متعدد الصفوف (Multi-Row Subquery)
يُرجع عدة صفوف (عمود واحد، صفوف متعددة). يُستخدم مع IN, ANY, ALL.
-- مثال: البحث عن الموظفين في أقسام معينة
SELECT emp_name, department
FROM employees
WHERE department IN (
SELECT department
FROM departments
WHERE location = 'الرياض'
);
3. الاستعلام الفرعي الجدولي (Table Subquery)
يُرجع جدول كامل (عدة صفوف وأعمدة). يُستخدم في FROM.
-- مثال: حساب متوسط الرواتب لكل قسم ثم إيجاد الأعلى
SELECT department, avg_salary
FROM (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
) AS dept_averages
WHERE avg_salary > 10000;
4. الاستعلام الفرعي المترابط (Correlated Subquery)
يعتمد على الاستعلام الخارجي ويُنفذ لكل صف من الاستعلام الرئيسي.
-- مثال: الموظفين الذين رواتبهم أعلى من متوسط قسمهم
SELECT e1.emp_name, e1.salary, e1.department
FROM employees e1
WHERE e1.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e1.department -- يعتمد على الصف الحالي
);
الفرق الأساسي:
- Subquery عادي: يُنفذ مرة واحدة فقط
- Correlated Subquery: يُنفذ لكل صف من الاستعلام الخارجي
أماكن استخدام الاستعلامات الفرعية
1. في جملة WHERE
الاستخدام الأكثر شيوعاً للتصفية.
SELECT emp_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
2. في جملة FROM
إنشاء جدول مؤقت (يُسمى Derived Table أو Inline View).
SELECT dept, max_sal
FROM (
SELECT department AS dept, MAX(salary) AS max_sal
FROM employees
GROUP BY department
) AS department_stats;
3. في جملة SELECT
حساب قيمة لكل صف.
SELECT
emp_name,
salary,
(SELECT AVG(salary) FROM employees) AS avg_salary,
salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;
4. في جملة HAVING
تصفية المجموعات بعد GROUP BY.
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > (
SELECT AVG(salary) FROM employees
);
أمثلة تمهيدية
إنشاء جداول للأمثلة:
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100),
department VARCHAR(50),
salary DECIMAL(10, 2),
hire_date DATE
);
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50),
location VARCHAR(50),
budget DECIMAL(12, 2)
);
INSERT INTO employees VALUES
(1, 'أحمد محمد', 'تطوير', 12000, '2020-01-15'),
(2, 'فاطمة علي', 'تطوير', 15000, '2019-03-20'),
(3, 'خالد حسن', 'تسويق', 9000, '2021-06-10'),
(4, 'سارة أحمد', 'تسويق', 11000, '2020-08-05'),
(5, 'محمد عبدالله', 'مبيعات', 8000, '2022-01-12'),
(6, 'نورا خالد', 'مبيعات', 8500, '2021-11-20');
INSERT INTO departments VALUES
(1, 'تطوير', 'الرياض', 500000),
(2, 'تسويق', 'جدة', 300000),
(3, 'مبيعات', 'الدمام', 250000);
مثال 1: قيمة واحدة (Scalar)
-- الموظفين الذين رواتبهم أعلى من المتوسط
SELECT emp_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
| emp_name | salary |
|---|---|
| أحمد محمد | 12000.00 |
| فاطمة علي | 15000.00 |
| سارة أحمد | 11000.00 |
مثال 2: قيم متعددة (IN)
-- الموظفين في الأقسام الموجودة في الرياض
SELECT emp_name, department
FROM employees
WHERE department IN (
SELECT dept_name
FROM departments
WHERE location = 'الرياض'
);
مثال 3: جدول مؤقت (FROM)
-- متوسط الرواتب لكل قسم، ثم عرض الأقسام فوق المتوسط العام
SELECT dept, avg_salary
FROM (
SELECT department AS dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
) AS dept_stats
WHERE avg_salary > (SELECT AVG(salary) FROM employees);
مثال 4: في SELECT
-- عرض كل موظف مع الفرق بين راتبه والمتوسط
SELECT
emp_name,
salary,
(SELECT AVG(salary) FROM employees) AS متوسط_الرواتب,
salary - (SELECT AVG(salary) FROM employees) AS الفرق
FROM employees
ORDER BY salary DESC;
الاستعلامات الفرعية مقابل JOIN
في كثير من الحالات، يمكن تحقيق نفس النتيجة باستخدام Subquery أو JOIN. لكن لكل منهما مزايا.
نفس النتيجة بطريقتين:
-- باستخدام Subquery
SELECT emp_name, department
FROM employees
WHERE department IN (
SELECT dept_name
FROM departments
WHERE location = 'الرياض'
);
-- باستخدام JOIN
SELECT e.emp_name, e.department
FROM employees e
INNER JOIN departments d ON e.department = d.dept_name
WHERE d.location = 'الرياض';
متى تستخدم Subquery؟
- عندما تحتاج قيمة محسوبة (مثل: AVG, MAX, COUNT)
- عندما تريد التحقق من وجود/عدم وجود بيانات (EXISTS, NOT EXISTS)
- عندما يكون الاستعلام أكثر وضوحاً بهذه الطريقة
- عندما لا تحتاج أعمدة من الجدول الثاني
متى تستخدم JOIN؟
- عندما تحتاج أعمدة من عدة جداول
- عندما تريد أداء أفضل (في معظم الحالات)
- عندما تريد ربط جداول متعددة
- عندما تحتاج جميع التطابقات
أخطاء شائعة
1. نسيان الأقواس
-- خطأ: بدون أقواس
SELECT * FROM employees
WHERE salary > SELECT AVG(salary) FROM employees;
-- صحيح: مع أقواس
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
2. استخدام قيم متعددة مع = بدلاً من IN
-- خطأ: = تتوقع قيمة واحدة
SELECT * FROM employees
WHERE department = (
SELECT dept_name FROM departments WHERE location = 'الرياض'
);
-- قد يُرجع عدة قيم!
-- صحيح: استخدم IN
SELECT * FROM employees
WHERE department IN (
SELECT dept_name FROM departments WHERE location = 'الرياض'
);
3. نسيان alias في FROM subquery
-- خطأ: بدون alias
SELECT dept, avg_sal
FROM (
SELECT department AS dept, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
);
-- صحيح: مع alias
SELECT dept, avg_sal
FROM (
SELECT department AS dept, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
) AS dept_averages;
أفضل الممارسات
1. استخدم أسماء واضحة للـ aliases
-- جيد
SELECT *
FROM (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
) AS department_averages;
2. تجنب Subqueries المعقدة جداً
إذا كان الاستعلام الفرعي معقداً جداً، فكر في:
- استخدام CTE (Common Table Expression) بدلاً منه
- تقسيمه إلى عدة خطوات
- استخدام JOIN إذا كان أوضح
3. انتبه للأداء
- Correlated Subqueries قد تكون بطيئة على الجداول الكبيرة
- استخدم EXPLAIN لفحص خطة التنفيذ
- فكر في استخدام JOIN إذا كان الأداء مشكلة
4. استخدم المعاملات المناسبة
- = للقيم الفردية
- IN للقيم المتعددة
- EXISTS للتحقق من الوجود
- ANY/ALL للمقارنات المتعددة
ملخص الدرس
في هذا الدرس التمهيدي، تعرفنا على أساسيات الاستعلامات الفرعية في SQL:
- Subquery هو استعلام داخل استعلام آخر
- يُوضع دائماً بين أقواس ( )
- أنواع رئيسية: Scalar, Multi-Row, Table, Correlated
- يمكن استخدامه في: WHERE, FROM, SELECT, HAVING
- مفيد للحسابات المعقدة والتصفية الديناميكية
- يمكن أن يكون بديلاً لـ JOIN في بعض الحالات
- انتبه للأداء خاصة مع Correlated Subqueries
- استخدم المعامل المناسب (=, IN, EXISTS, ANY, ALL)
الدروس القادمة:
في الدروس التالية، سنتعمق في كل نوع من أنواع الاستعلامات الفرعية:
- Subqueries في WHERE: أمثلة متقدمة مع IN, EXISTS, ANY, ALL
- Subqueries في FROM: Derived Tables وتطبيقاتها
- Correlated Subqueries: كيف تعمل ومتى تستخدمها
- EXISTS و NOT EXISTS: التحقق من وجود البيانات
نصيحة: الاستعلامات الفرعية أداة قوية، لكن لا تفرط في استخدامها. اختر دائماً الطريقة الأكثر وضوحاً وأداءً لحل المشكلة.
الخطوة التالية: الاستعلامات الفرعية في WHERE
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الاستعلامات الفرعية في WHERE وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي