MySQL استعلام فرعي مع أمثلة

⚡ ملخص ذكي

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

  • 🔍 التعريف الأساسي: الاستعلام الفرعي هو عبارة SELECT متداخلة داخل استعلام آخر، ويتم تشغيل الاستعلام الداخلي أولاً لتوفير القيم للاستعلام الخارجي.
  • 🧮 استعلام فرعي قياسي: تقوم هذه الدالة بإرجاع صف واحد وعمود واحد، لذا فهي تقترن بعوامل المقارنة مثل يساوي، أكبر من، أو أصغر من.
  • 📋 الاستعلامات الفرعية للصفوف والجداول: تُرجع الاستعلامات الفرعية للصفوف صفًا واحدًا يحتوي على عدة أعمدة، بينما تُرجع الاستعلامات الفرعية للجداول العديد من الصفوف وتعمل مع عامل التشغيل IN.
  • 🧩 عمق التعشيش: يمكن تداخل الاستعلامات الفرعية على عدة مستويات عميقة، مما يحدد قيمًا مثل العضو الأعلى دفعًا في بيان واحد.
  • ✍️ ما وراء الاختيار: تقبل عبارات INSERT و UPDATE و DELETE الاستعلامات الفرعية، مما يجعل التغييرات الجماعية ممكنة بدون جداول مؤقتة.
  • قاعدة الأداء: عادةً ما يتم تنفيذ عملية الربط (JOIN) بشكل أسرع بكثير من الاستعلام الفرعي المكافئ، لذا احتفظ بالاستعلامات الفرعية للمنطق الذي لا يمكن لعملية الربط (JOIN) التعبير عنه.

MySQL استعلام فرعي

ما هو الاستعلام الفرعي في لغة SQL؟

A استعلام فرعي هو استعلام SELECT مُضمّن داخل استعلام آخر. يُستخدم استعلام SELECT الداخلي عادةً لتحديد نتائج استعلام SELECT الخارجي، لذا تُقيّم قاعدة البيانات العبارة الداخلية أولاً ثم تُمرّر مُخرجاتها إلى الأعلى.

يُطلق على الاستعلام الداخلي اسم استعلام داخلي أو استعلام متداخل، وتسمى العبارة التي تحتوي عليه بـ استعلام خارجيدعونا نلقي نظرة على صيغة الاستعلام الفرعي.

MySQL استعلام فرعي

يوضح الرسم التخطيطي أعلاه الشكل العام للعبارة: يوفر 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 استعلام فرعي

دعونا نرى كيف يعمل هذا الاستعلام.

MySQL استعلام فرعي

كما يوضح مخطط التنفيذ، 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);

MySQL استعلام فرعي

دعونا نرى كيف يعمل هذا الاستعلام.

MySQL استعلام فرعي

في هذه الحالة، تُرجع الاستعلامات الداخلية أكثر من نتيجة واحدة، لذا يتم تمرير قائمة أرقام العضوية إلى IN يتم إرجاع كل عنصر مطابق وعامل التشغيل.

استعلامات فرعية متداخلة على عدة مستويات عميقة

لقد رأيت حتى الآن مستويين. قد يحتوي الاستعلام الفرعي أيضًا على استعلام فرعي آخر، مما ينتج عنه عبارة متداخلة ثلاثية.

لنفترض أن الإدارة ترغب في مكافأة العضو الأعلى أجراً. يمكننا تنفيذ استعلام كهذا:

SELECT full_names FROM members
WHERE membership_number = (SELECT membership_number FROM payments
    WHERE amount_paid = (SELECT MAX(amount_paid) FROM payments));

يُحدد الاستعلام الداخلي أكبر دفعة، ويُحوّل الاستعلام الأوسط هذا المبلغ إلى رقم عضوية، ويُحوّل الاستعلام الخارجي رقم العضوية إلى اسم. يُعطي الاستعلام أعلاه النتيجة التالية:

MySQL استعلام فرعي

كيفية استخدام الاستعلامات الفرعية مع عمليات الإدراج والتحديث والحذف

لا تقتصر الاستعلامات الفرعية على عبارات 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) لتحقيق ما سبق.

الاستعلامات الفرعية مقابل الانضمامات

يسهل أيضًا تقسيم الاستعلامات الفرعية إلى مكونات منطقية منفردة، وهو أمر مفيد للغاية عندما تجريب وتصحيح الاستعلامات.

الأسئلة الشائعة

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

يمكن أن يظهر الاستعلام الفرعي في جملة WHERE، أو جملة HAVING، أو قائمة SELECT، أو جملة FROM، حيث يصبح جدولًا مشتقًا ويتطلب اسمًا بديلًا. كما أنه صالح أيضًا داخل عبارات INSERT وUPDATE وDELETE.

نعم، في كثير من الأحيان. مساعدو الذكاء الاصطناعي المدمجون في برامج التحرير مثل MySQL طاولة العمل يمكن اقتراح ما يعادل ذلك الانضمامقارن دائمًا عدد الصفوف واقرأ خطة EXPLAIN قبل الوثوق بإعادة الكتابة، لأن معالجة القيم الفارغة قد تختلف.

نعم. تُحوّل أدوات تحويل النص إلى SQL سؤالاً مثل "ما هي الفئة التي تضم أقل عدد من الأفلام؟" إلى استعلام SELECT متداخل. وتعتمد الدقة على المخطط المُقدّم للنموذج، لذا يُرجى مراجعة العبارة المُولّدة ومقارنتها بأسماء الجداول الحقيقية.

يظهر الخطأ عندما تُرجع استعلام فرعي موضوع بعد عامل مقارنة عدة صفوف. استبدل العامل بـ IN أو ANY أو EXISTS، أو حسّن شرط WHERE الداخلي بحيث يُرجع صفًا واحدًا فقط.

تلخيص هذه التدوينة بـ: