MySQL استعلام فرعي مع أمثلة
⚡ ملخص ذكي
MySQL تضع صيغة الاستعلام الفرعي عبارة SELECT داخل عبارة أخرى، بحيث تُغذي النتيجة الداخلية الاستعلام الخارجي. يشمل هذا الشرح الاستعلامات الفرعية العددية، واستعلامات الصفوف، واستعلامات الجداول، وترتيب التنفيذ، وأمثلة عملية، وموازنة الأداء مع عمليات الربط (JOIN).

ما هو الاستعلام الفرعي في لغة SQL؟
A استعلام فرعي هو استعلام SELECT مُضمّن داخل استعلام آخر. يُستخدم استعلام SELECT الداخلي عادةً لتحديد نتائج استعلام SELECT الخارجي، لذا تُقيّم قاعدة البيانات العبارة الداخلية أولاً ثم تُمرّر مُخرجاتها إلى الأعلى.
يُطلق على الاستعلام الداخلي اسم استعلام داخلي أو استعلام متداخل، وتسمى العبارة التي تحتوي عليه بـ استعلام خارجيدعونا نلقي نظرة على صيغة الاستعلام الفرعي.
يوضح الرسم التخطيطي أعلاه الشكل العام للعبارة: يوفر SELECT الخارجي الأعمدة التي تريد رؤيتها، ويوفر SELECT الداخلي بين قوسين القيمة أو قائمة القيم التي تقارن بها عبارة WHERE.
لماذا نستخدم الاستعلام الفرعي؟
قبل النظر في الأنواع المختلفة، من المفيد معرفة متى يستحق الاستعلام الفرعي مكانه في البيان.
يجيب الاستعلام الفرعي على سؤال لا تُعرف قيمة فلترته مسبقًا، بل تُحسب من البيانات نفسها. من الشكاوى الشائعة لدى عملاء مكتبة MyFlix للفيديوهات قلة عدد عناوين الأفلام، وترغب الإدارة في شراء أفلام من الفئة الأقل عددًا. لا أحد يعرف هذه الفئة إلا بعد الاستعلام من قاعدة البيانات، لذا يجب حساب القيمة أولًا ثم استخدامها كفلتر.
الاستعلامات الفرعية موجودة فيtracفعال لثلاثة أسباب عملية:
- قراءة: يقع كل جزء من المنطق في كتلة خاصة به بين قوسين، لذا فإن العبارة تُقرأ كسلسلة من الأسئلة الصغيرة بدلاً من تعبير واحد معقد.
- عزل: يمكن تشغيل استعلام داخلي بمفرده للتأكد من أنه يُرجع القيمة المتوقعة، مما يجعل الاختبار والتصحيح أسهل بكثير.
- المرونة: ينطبق النمط نفسه في عبارات WHERE و HAVING و SELECT و FROM، وكذلك داخل عبارات INSERT و UPDATE و DELETE.
المقايضة تكمن في السرعة، والتي سيتم فحصها في مقارنة JOIN لاحقًا في هذه المقالة.
أنواع الاستعلامات الفرعية في MySQL
MySQL يدعم ثلاثة أنواع من الاستعلامات الفرعية، ويُحدد النوع بناءً على شكل النتيجة التي يُرجعها الاستعلام الداخلي. يُشرح كل نوع أدناه مع مثال عملي على قاعدة بيانات myflixdb.
1) استعلام فرعي عددي
A استعلام فرعي عددي تُرجع هذه الدالة صفًا واحدًا وعمودًا واحدًا فقط، أي أنها تُرجع قيمة واحدة. ولأن النتيجة قيمة واحدة، يُمكن استخدامها في أي مكان يُسمح فيه باستخدام قيمة حرفية. بالعودة إلى مشكلة MyFlix المذكورة أعلاه، يُمكنك استخدام استعلام كهذا:
SELECT category_name FROM categories WHERE category_id = (SELECT MIN(category_id) FROM movies);
ويعطي نتيجة:
دعونا نرى كيف يعمل هذا الاستعلام.
كما يوضح مخطط التنفيذ، MySQL أول أشواط SELECT MIN(category_id) FROM moviesيستقبل قيمة واحدة، ثم يُنفّذ الاستعلام الخارجي باستخدام تلك القيمة. ولأن القيمة المُعادة واحدة، فإن عوامل التشغيل المسموح بها هي مجموعة المقارنة القياسية: =, <> (أو !=), >, >=, <و <=.
💡 نصيحة: إذا تم وضع استعلام فرعي بعد = يُرجع أكثر من صف واحد، MySQL يُظهر الخطأ 1242، الاستعلام الفرعي يُرجع أكثر من صف واحدحوّل المشغل إلى INأو قم بتضييق نطاق شرط WHERE الداخلي.
2) استعلام فرعي للصف
A استعلام فرعي للصف كما أنها تُرجع صفًا واحدًا، ولكن قد يحتوي هذا الصف على أكثر من عمود. لذلك، يقارن الاستعلام الخارجي صفًا من القيم مع مُنشئ الصف بدلاً من مقارنته بقيمة واحدة.
SELECT full_names, contact_number FROM members WHERE (membership_number, gender) = (SELECT membership_number, gender FROM members WHERE full_names = 'Janet Jones');
العوامل المسموح بها هي نفس عوامل المقارنة المذكورة أعلاه، ويتم تطبيقها على الصف بأكمله دفعة واحدة.
3) استعلام فرعي للجدول
A استعلام فرعي للجدول تُرجع عدة صفوف، وغالبًا عدة أعمدة، لذا يجب أن يستخدم الاستعلام الخارجي عامل مجموعة مثل IN, NOT IN, ANY, ALL أو EXISTS.
لنفترض أنك تريد أسماء وأرقام هواتف الأعضاء الذين استأجروا فيلمًا ولم يعيدوه بعد، حتى تتمكن من الاتصال بهم لتذكيرهم. يمكنك استخدام استعلام كهذا:
SELECT full_names, contact_number FROM members WHERE membership_number IN (SELECT membership_number FROM movierentals WHERE return_date IS NULL);
دعونا نرى كيف يعمل هذا الاستعلام.
في هذه الحالة، تُرجع الاستعلامات الداخلية أكثر من نتيجة واحدة، لذا يتم تمرير قائمة أرقام العضوية إلى IN يتم إرجاع كل عنصر مطابق وعامل التشغيل.
استعلامات فرعية متداخلة على عدة مستويات عميقة
لقد رأيت حتى الآن مستويين. قد يحتوي الاستعلام الفرعي أيضًا على استعلام فرعي آخر، مما ينتج عنه عبارة متداخلة ثلاثية.
لنفترض أن الإدارة ترغب في مكافأة العضو الأعلى أجراً. يمكننا تنفيذ استعلام كهذا:
SELECT full_names FROM members WHERE membership_number = (SELECT membership_number FROM payments WHERE amount_paid = (SELECT MAX(amount_paid) FROM payments));
يُحدد الاستعلام الداخلي أكبر دفعة، ويُحوّل الاستعلام الأوسط هذا المبلغ إلى رقم عضوية، ويُحوّل الاستعلام الخارجي رقم العضوية إلى اسم. يُعطي الاستعلام أعلاه النتيجة التالية:
كيفية استخدام الاستعلامات الفرعية مع عمليات الإدراج والتحديث والحذف
لا تقتصر الاستعلامات الفرعية على عبارات SELECT فقط. يعمل النمط نفسه بين قوسين داخل عبارات تعديل البيانات، مما يسمح بتغيير مجموعة كاملة من الصفوف في خطوة واحدة دون إنشاء جدول مؤقت.
أدخل باستخدام استعلام فرعي. يمكن للاستعلام الفرعي تحديد الصفوف المراد إدراجها، وذلك بنسخ البيانات من جدول إلى آخر. يجب أن تتطابق قائمة أعمدة استعلام SELECT مع قائمة أعمدة استعلام INSERT.
INSERT INTO vip_members (membership_number, full_names) SELECT membership_number, full_names FROM members WHERE membership_number IN (SELECT membership_number FROM payments WHERE amount_paid > 5000);
تحديث باستخدام استعلام فرعي. هنا، يحدد الاستعلام الداخلي الصفوف التي سيتم تعديلها. المثال أدناه يُشير إلى كل عضو لم يسدد إيجاره بعد.
UPDATE members SET reminder_sent = 1 WHERE membership_number IN (SELECT membership_number FROM movierentals WHERE return_date IS NULL);
حذف باستخدام استعلام فرعي. تقوم الفكرة نفسها بإزالة الصفوف التي تحقق شرطًا موجودًا في جدول ثانٍ.
DELETE FROM members WHERE membership_number NOT IN (SELECT membership_number FROM movierentals);
⚠️ تحذير: MySQL لا يسمح هذا الأمر بتعديل جدول ثم تحديد بيانات منه داخل استعلام فرعي في عبارة FROM. في حال ظهور الخطأ 1093، قم بتغليف الاستعلام الداخلي في جدول مشتق، على سبيل المثال SELECT * FROM (SELECT ...) AS t، بحيث MySQL يُظهر النتيجة قبل تطبيق التغيير. من الحكمة أيضًا تشغيل استعلام SELECT الداخلي بشكل منفصل أولًا، والتأكد من عدد الصفوف، قبل تشغيل الاستعلام. قم أو حذف في الانتاج.
الاستعلامات الفرعية مقابل عمليات الربط
يمكن لكل من الاستعلام الفرعي و JOIN دمج المعلومات من أكثر من جدول، لذا فإن السؤال الطبيعي هو أي منهما يجب اللجوء إليه.
بالمقارنة مع عمليات الربط، فإن الاستعلامات الفرعية سهلة الاستخدام والقراءة. فهي ليست معقدة مثل ينضموبالتالي، يتم استخدامها بشكل متكرر من قبل مبتدئين SQL.
لكن الاستعلامات الفرعية تعاني من مشاكل في الأداء. استخدام الربط بدلاً من الاستعلام الفرعي قد يُحسّن الأداء أحيانًا بما يصل إلى 500 ضعف، لأن المُحسِّن يستطيع إتمام الربط في تمريرة واحدة بدلاً من تقييم العبارة الداخلية بشكل متكرر.
| نقطة المقارنة | استعلام فرعي | الانضمام |
|---|---|---|
| قراءة | مرتفع، حيث يجيب كل مربع على سؤال واحد | في الأسفل، حيث تظهر جميع الجداول في جملة واحدة |
| هاملت | قد يتم تشغيل الاستعلام الداخلي بشكل أبطأ لكل صف خارجي | أسرع، وغالبًا بفارق كبير جدًا |
| أعمدة النتائج | يتم إرجاع أعمدة الجدول الخارجي فقط | يمكن إرجاع الأعمدة من كل جدول مرتبط |
| الاستخدام النموذجي | تصفية البيانات بناءً على قيمة يجب حسابها أولاً | دمج الصفوف ذات الصلة من جدولين أو أكثر |
| منحنى التعلم | لطيف، مألوف للمبتدئين | أكثر انحدارًا، يتطلب معرفة بأنواع الربط |
عند الاختيار، يوصى باستخدام JOIN عبر استعلام فرعي. ينبغي استخدام الاستعلامات الفرعية فقط كحل احتياطي عندما يتعذر استخدام عملية الربط (JOIN) لتحقيق ما سبق.
يسهل أيضًا تقسيم الاستعلامات الفرعية إلى مكونات منطقية منفردة، وهو أمر مفيد للغاية عندما تجريب وتصحيح الاستعلامات.






