طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 121 · مبادئ قواعد البيانات
الدرس 26 · الوحدة 12: تنفيذ استعلامات محسّنة

الاستعلامات الفرعية والمشروع الختامي

أسئلة مركّبة باستعلام داخل استعلام، ثم مشروع يجمع المقرر كله. ⏱ نحو 35 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تكتب استعلامًا فرعيًّا قياسيًّا يعيد قيمة واحدة.
  • تستخدم IN و NOT IN و EXISTS مع الاستعلامات الفرعية، وتتجنب فخ NULL.
  • تكتب استعلامًا مرتبطًا وجدولًا مشتقًّا وتعبير WITH.
  • تختار بين الاستعلام الفرعي و JOIN، وتخطط للمشروع الختامي.

١استعلام داخل استعلام

الاستعلام الفرعي Subquery: استعلام بين قوسين داخل استعلام آخر، يُحسب أولًا وتُستخدم نتيجته. «المنتجات التي سعرها أعلى من متوسط الأسعار»: لا نعرف المتوسط، فنتركه لاستعلام فرعي:

query.sql
SELECT name, priceFROM productsWHERE price > (SELECT AVG(price) FROM products)ORDER BY price DESC;
النتيجة · 4 صفوف
nameprice
عسل سدر 500غ150
مبخرة120
تمر عجوة 1كغ90
دلة قهوة75

الاستعلام الفرعي هنا يعيد قيمة واحدة (المتوسط)، لذلك نقارنها بـ >. ويسمى استعلامًا فرعيًّا قياسيًّا Scalar.

٢IN و NOT IN: قائمة من استعلام

حين يعيد الاستعلام الفرعي عمودًا من قيم كثيرة، نستخدم IN. «المنتجات التي لم يطلبها أحد قط»:

query.sql
SELECT name, stockFROM productsWHERE id NOT IN (SELECT product_id FROM order_items);
النتيجة · صف واحد
namestock
تمر سكري 1كغ0

٣EXISTS والاستعلام المرتبط

الاستعلام الفرعي المرتبط Correlated يستخدم عمودًا من الاستعلام الخارجي، فيُنفَّذ مرة لكل صف. «العملاء الذين طلبوا في مايو 2025»:

query.sql
SELECT nameFROM customers cWHERE EXISTS (  SELECT 1 FROM orders o  WHERE o.customer_id = c.id          -- مرتبط بالعميل الحالي    AND o.order_date >= '2025-05-01');
النتيجة · 3 صفوف
name
سعود العتيبي
خالد الحربي
ماجد الغامدي

ومثال مرتبط آخر: «المنتجات الأغلى من متوسط تصنيفها»؛ المتوسط يتغير بتغير تصنيف المنتج:

query.sql
SELECT name, category, priceFROM products pWHERE price > (SELECT AVG(price) FROM products WHERE category = p.category)ORDER BY category;
النتيجة · 5 صفوف
namecategoryprice
مبخرةأدوات120
زعفران 5غبهارات55
تمر عجوة 1كغتمور90
عسل سدر 500غعسل150
قهوة عربية 250غمشروبات35

٤الجدول المشتق و WITH

يمكن أن يكون الاستعلام الفرعي في FROM، فتعامل نتيجته كأنها جدول. «متوسط قيمة الطلب وأكبر طلب»: نحسب إجمالي كل طلب أولًا، ثم نلخّص الإجماليات. والصياغة الأوضح بـ WITH (تعبير الجدول المؤقت CTE):

query.sql
WITH totals AS (  SELECT order_id, SUM(qty * price) AS total  FROM order_items  GROUP BY order_id)SELECT ROUND(AVG(total), 2) AS avg_order,       MAX(total)           AS max_orderFROM totals;
النتيجة · صف واحد
avg_ordermax_order
144.67240

WITH مدعوم في MySQL 8 و SQLite و PostgreSQL. وهو يقسّم الاستعلام الطويل إلى خطوات مسمّاة يسهل قراءتها وتصحيحها.

٥الاستعلام الفرعي أم JOIN؟

كثير من الاستعلامات الفرعية يمكن كتابتها بـ JOIN، والعكس. «عدد طلبات كل عميل» بالطريقتين:

✗ خطأ
query.sql
-- استعلام فرعي لكل عميلSELECT name,  (SELECT COUNT(*) FROM orders o   WHERE o.customer_id = c.id) AS orders_countFROM customers c;
النتيجة · 10 صفوف
nameorders_count
سعود العتيبي3
نورة القحطاني2
خالد الحربي2
ريم الشهري1
فهد الدوسري1
سارة المطيري1
عبدالله الزهراني0
هند السبيعي1
ماجد الغامدي1
لمى العنزي0
✓ صواب
query.sql
-- ربط وتجميع مرة واحدةSELECT c.name, COUNT(o.id) AS orders_countFROM customers cLEFT JOIN orders o ON o.customer_id = c.idGROUP BY c.id;
النتيجة · 10 صفوف
nameorders_count
سعود العتيبي3
نورة القحطاني2
خالد الحربي2
ريم الشهري1
فهد الدوسري1
سارة المطيري1
عبدالله الزهراني0
هند السبيعي1
ماجد الغامدي1
لمى العنزي0

كلاهما صحيح ونتيجتهما واحدة. الأول يُنفَّذ مرة لكل عميل، والثاني يجمع مرة واحدة. في الجداول الكبيرة قارن خطتيهما وزمنيهما واختر الأسرع. والمحسّنات الحديثة كثيرًا ما تحوّل أحدهما إلى الآخر تلقائيًّا.

استخدمحين
الاستعلام الفرعي القياسيتحتاج قيمة واحدة للمقارنة: متوسط، أكبر قيمة.
EXISTS / NOT EXISTSالسؤال «هل يوجد؟» بلا حاجة لأعمدة من الجدول الآخر.
JOINتحتاج أعمدة من الجدولين معًا في النتيجة.
WITHالاستعلام طويل وله خطوات.

٦المشروع الختامي

الآن تجمع كل ما تعلمته في مشروع واحد تنفّذه في المعمل على MySQL، في مجموعات من طالبين أو ثلاثة. اختر نظامًا من هذه، أو اقترح نظامًا آخر يوافق عليه المعلم:

المرحلةالمطلوبالوحدات
1. التحليل والتصميممتطلبات النظام، ومخطط ERD (4 كيانات أو أكثر، وعلاقة متعدد لمتعدد واحدة على الأقل).4، 6
2. الإنشاءCREATE TABLE بأنواع مناسبة، ومفاتيح أساسية وأجنبية، وقيود NOT NULL و UNIQUE و CHECK و DEFAULT.3، 5
3. البيانات15 صفًّا أو أكثر في كل جدول رئيس بـ INSERT، وأمثلة UPDATE و DELETE داخل معاملات.8، 9
4. الاستعلامات10 استعلامات تجيب عن أسئلة حقيقية: تصفية، وترتيب، وتجميع مع HAVING، وربط 3 جداول، واستعلام فرعي.7، 12
5. الأداءاستعلامان على جدول كبير (ولّد 50 ألف صف أو أكثر): الزمن والخطة قبل الفهرس وبعده، ونسبة التحسن.10، 11
6. التسليمملف .sql يعمل من أوله لآخره، وصورة ERD، وتقرير قصير بنتائج القياس.—
🧪 ملعب SQLجرّب فيها استعلامات مشروعك قبل كتابتها في MySQL.

توقّع وتحقّق

استعلام فرعي يعيد قيمة واحدة نقارنه بـ:

قياسي.

«المنتجات التي لم تُطلب قط»:

ليست في القائمة.

الاستعلام المرتبط:

يُنفَّذ لكل صف.

WITH تُستخدم لـ:

CTE.

الخلاصة

تحدٍّ برمجي: أفضل العملاء

متقدم

الإدارة تريد مكافأة العملاء الذين يزيد إنفاقهم على متوسط إنفاق العملاء. احسب الإنفاق من أصناف الطلبات غير الملغاة.

المطلوب:
  • الإنفاق = مجموع qty * price في order_items لطلبات العميل التي حالتها ليست «ملغى».
  • متوسط الإنفاق: متوسط إنفاق العملاء الذين لهم طلبات غير ملغاة.
  • اعرض name و spent للعملاء الأعلى من المتوسط، مرتبين تنازليًّا بالإنفاق.
  • استخدم WITH أو استعلامًا فرعيًّا.
👀 اعرض النتيجة المطلوبة
النتيجة المطلوبة
namespent
سعود العتيبي383
نورة القحطاني295
خالد الحربي240
سارة المطيري240
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء
  • النتيجة: العملاء الأعلى من المتوسط وإنفاقهم
  • يستخدم WITH أو استعلامًا فرعيًّا

💡 تلميح 1
الخطوة 1 (داخل WITH): اربط orders بـ order_items، واستبعد الملغى، وجمّع بـ customer_id.
💡 تلميح 2
الخطوة 2: WHERE s.spent > (SELECT AVG(spent) FROM spending)

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

solution.sql
WITH spending AS (  SELECT o.customer_id, SUM(oi.qty * oi.price) AS spent  FROM orders o  JOIN order_items oi ON oi.order_id = o.id  WHERE o.status <> 'ملغى'  GROUP BY o.customer_id)SELECT c.name, s.spentFROM spending sJOIN customers c ON c.id = s.customer_idWHERE s.spent > (SELECT AVG(spent) FROM spending)ORDER BY s.spent DESC;
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس