MySQL وتصميم قواعد البيانات

الغوص العميق في الاستعلامات الفرعية

13 دقيقة الدرس 7 من 40

الغوص العميق في الاستعلامات الفرعية

الاستعلامات الفرعية (المعروفة أيضاً بالاستعلامات المتداخلة أو الاستعلامات الداخلية) هي تراكيب SQL قوية تسمح لك باستخدام نتيجة استعلام واحد داخل استعلام آخر. في هذا الدرس، سنستكشف الأنواع المختلفة من الاستعلامات الفرعية ومتى نستخدمها بفعالية.

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

الاستعلام الفرعي هو عبارة SELECT متداخلة داخل عبارة SQL أخرى. يمكن استخدام الاستعلامات الفرعية في جمل SELECT و INSERT و UPDATE و DELETE و FROM.

مفهوم أساسي: يتم تنفيذ الاستعلامات الفرعية أولاً، ويتم تمرير نتائجها إلى الاستعلام الخارجي. فكر فيها كجداول مؤقتة موجودة فقط طوال مدة الاستعلام.

أنواع الاستعلامات الفرعية

يدعم MySQL عدة أنواع من الاستعلامات الفرعية، كل منها يخدم أغراضاً مختلفة:

1. الاستعلامات القياسية (Scalar): تُرجع قيمة واحدة (صف واحد، عمود واحد) 2. استعلامات الأعمدة: تُرجع عموداً واحداً بصفوف متعددة 3. استعلامات الصفوف: تُرجع صفاً واحداً بأعمدة متعددة 4. استعلامات الجداول: تُرجع مجموعة نتائج كاملة (صفوف وأعمدة متعددة) 5. الاستعلامات المترابطة: تشير إلى أعمدة من الاستعلام الخارجي

1. الاستعلامات القياسية

تُرجع الاستعلامات القياسية قيمة واحدة بالضبط ويمكن استخدامها حيثما يُتوقع قيمة واحدة:

-- البحث عن المنتجات بسعر أعلى من المتوسط SELECT product_name, price FROM products WHERE price > ( SELECT AVG(price) FROM products );
-- عرض المنتج مع فرق السعر من المتوسط SELECT product_name, price, price - (SELECT AVG(price) FROM products) AS price_diff FROM products;

2. استعلامات الأعمدة (IN, ANY, ALL)

تُرجع استعلامات الأعمدة قيماً متعددة في عمود واحد وتُستخدم عادةً مع عوامل IN أو ANY أو ALL:

-- البحث عن العملاء الذين قدموا طلبات في 2023 SELECT customer_name FROM customers WHERE customer_id IN ( SELECT DISTINCT customer_id FROM orders WHERE YEAR(order_date) = 2023 );
-- البحث عن المنتجات أغلى من جميع المنتجات في فئة "Books" SELECT product_name, price FROM products WHERE price > ALL ( SELECT price FROM products WHERE category = 'Books' );
نصيحة: استخدم IN عند التحقق من العضوية في مجموعة. استخدم ANY/SOME عند المقارنة بقيمة واحدة على الأقل. استخدم ALL عند المقارنة بكل قيمة في المجموعة.

3. استعلامات الصفوف

تُرجع استعلامات الصفوف صفاً واحداً بأعمدة متعددة:

-- البحث عن منتج يطابق مزيج معين من السعر والمخزون SELECT product_name FROM products WHERE (price, stock_quantity) = ( SELECT MIN(price), MAX(stock_quantity) FROM products WHERE category = 'Electronics' );

4. استعلامات الجداول (جملة FROM)

تُرجع استعلامات الجداول صفوفاً وأعمدة متعددة، وغالباً ما تُستخدم في جملة FROM كجداول مشتقة:

-- حساب متوسط قيمة الطلب حسب العميل SELECT customer_name, avg_order_value FROM customers c JOIN ( SELECT customer_id, AVG(total_amount) AS avg_order_value FROM orders GROUP BY customer_id ) AS order_stats ON c.customer_id = order_stats.customer_id WHERE avg_order_value > 100;
-- البحث عن أفضل 3 منتجات في كل فئة SELECT * FROM ( SELECT product_name, category, price, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rank_in_category FROM products ) AS ranked_products WHERE rank_in_category <= 3;

5. الاستعلامات المترابطة

تشير الاستعلامات المترابطة إلى أعمدة من الاستعلام الخارجي ويتم تنفيذها مرة واحدة لكل صف في الاستعلام الخارجي:

-- البحث عن المنتجات الأغلى في فئتها SELECT p1.product_name, p1.category, p1.price FROM products p1 WHERE p1.price = ( SELECT MAX(p2.price) FROM products p2 WHERE p2.category = p1.category );
-- البحث عن العملاء الذين قدموا طلبات أكثر من متوسط قيمة طلباتهم SELECT c.customer_name, o.order_id, o.total_amount FROM customers c JOIN orders o ON c.customer_id = o.customer_id WHERE o.total_amount > ( SELECT AVG(total_amount) FROM orders o2 WHERE o2.customer_id = c.customer_id );
تحذير الأداء: يمكن أن تكون الاستعلامات المترابطة بطيئة لأنها تُنفذ مرة واحدة لكل صف في الاستعلام الخارجي. فكر في استخدام JOINs أو دوال النافذة كبدائل عندما يكون ذلك ممكناً.

EXISTS و NOT EXISTS

يختبر EXISTS ما إذا كان الاستعلام الفرعي يُرجع أي صفوف. غالباً ما يكون أكثر كفاءة من IN لمجموعات البيانات الكبيرة:

-- البحث عن العملاء الذين قدموا طلباً واحداً على الأقل SELECT customer_name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id );
-- البحث عن المنتجات التي لم يتم طلبها أبداً SELECT product_name FROM products p WHERE NOT EXISTS ( SELECT 1 FROM order_items oi WHERE oi.product_id = p.product_id );
نصيحة التحسين: SELECT 1 في جمل EXISTS هو اصطلاح - يتحقق MySQL فقط من وجود صفوف، وليس ما يتم تحديده. هذا يجعل EXISTS فعالاً جداً.

الاستعلامات الفرعية في جمل مختلفة

-- استعلام فرعي في SELECT (قياسي) SELECT product_name, (SELECT COUNT(*) FROM order_items WHERE product_id = p.product_id) AS times_ordered FROM products p; -- استعلام فرعي في WHERE SELECT product_name FROM products WHERE category_id IN (SELECT category_id FROM categories WHERE is_active = 1); -- استعلام فرعي في HAVING SELECT category, COUNT(*) AS product_count FROM products GROUP BY category HAVING COUNT(*) > (SELECT AVG(cnt) FROM (SELECT COUNT(*) AS cnt FROM products GROUP BY category) AS counts);

اعتبارات الأداء

أفضل الممارسات:
  • استخدم EXISTS بدلاً من IN للفحوصات المترابطة مع مجموعات البيانات الكبيرة
  • فكر في JOINs كبدائل - غالباً ما تكون أسرع
  • تجنب الاستعلامات الفرعية في جملة SELECT عند معالجة العديد من الصفوف
  • استخدم EXPLAIN لتحليل أداء الاستعلام الفرعي
  • أنشئ فهارس مناسبة على الأعمدة المستخدمة في الاستعلامات الفرعية

مثال من العالم الحقيقي: تحليلات التجارة الإلكترونية

-- البحث عن المنتجات التي تؤدي أفضل من متوسط الفئة SELECT p.product_name, p.category, p.sales_count, (SELECT AVG(sales_count) FROM products p2 WHERE p2.category = p.category) AS category_avg, p.sales_count - (SELECT AVG(sales_count) FROM products p2 WHERE p2.category = p.category) AS diff_from_avg FROM products p WHERE p.sales_count > ( SELECT AVG(sales_count) FROM products p2 WHERE p2.category = p.category ) ORDER BY category, diff_from_avg DESC;

تمرين عملي:

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

  1. البحث عن جميع العملاء الذين أنفقوا أكثر من متوسط الإنفاق الإجمالي
  2. سرد المنتجات التي لم يتم طلبها أبداً باستخدام EXISTS
  3. البحث عن ثاني أعلى راتب في كل قسم

تلميحات الحل:

-- 1. إنفاق العملاء أعلى من المتوسط SELECT customer_name, total_spent FROM ( SELECT c.customer_name, SUM(o.total_amount) AS total_spent FROM customers c JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.customer_name ) AS customer_spending WHERE total_spent > (SELECT AVG(total_spent) FROM (...)); -- 2. المنتجات التي لم يتم طلبها أبداً SELECT product_name FROM products p WHERE NOT EXISTS (SELECT 1 FROM order_items WHERE product_id = p.product_id); -- 3. ثاني أعلى راتب SELECT department, MAX(salary) AS second_highest FROM employees e1 WHERE salary < (SELECT MAX(salary) FROM employees e2 WHERE e2.department = e1.department) GROUP BY department;

الملخص

في هذا الدرس، تعلمت:

  • خمسة أنواع من الاستعلامات الفرعية: قياسية، أعمدة، صفوف، جداول، ومترابطة
  • كيفية استخدام IN و ANY و ALL و EXISTS و NOT EXISTS مع الاستعلامات الفرعية
  • الاستعلامات المترابطة تشير إلى أعمدة الاستعلام الخارجي
  • اعتبارات الأداء وتقنيات التحسين
  • متى تستخدم الاستعلامات الفرعية مقابل JOINs
التالي: في الدرس التالي، سنتقن تقنيات JOIN المتقدمة بما في ذلك الانضمامات الذاتية، والانضمامات لجداول متعددة، واستراتيجيات تحسين الانضمام!

ES
Edrees Salih
منذ 15 ساعة

We are still cooking the magic in the way!