طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 122 · برمجة قواعد البيانات
الدرس 22 · الوحدة 10: تحسين الاستعلامات باستخدام لغة البرمجة الإجرائية

التفكير بالمجموعات بدل الحلقات

أهم عادة في تحسين الأداء: صف النتيجة كلها بأمر واحد بدل حلقة. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح لماذا المعالجة صفًّا بصف أبطأ من أمر المجموعة.
  • تحوّل حلقة إلى UPDATE … FROM أو INSERT … SELECT.
  • تستبدل IF داخل الحلقة بـ CASE.
  • تميز الحالات التي تبقى فيها الحلقة مناسبة.

١صف بصف: بطء تراه بعينك

المبرمج القادم من لغات مثل Python يميل إلى الحلقة: «لكل عميل، احسب مشترياته، ثم حدّثه». هذا يعمل، لكنه ينفّذ آلاف الاستعلامات الصغيرة. قاعدة البيانات مصممة لتعالج المجموعة كلها في أمر واحد. قارن الزمنين على 3000 عميل:

query.sql
CREATE INDEX ON orders (customer_id);ALTER TABLE customers ADD COLUMN spent numeric; -- الطريقة الإجرائية: حلقة على 3000 عميلDO $$DECLARE  r record;BEGIN  FOR r IN SELECT id FROM customers LOOP    UPDATE customers SET spent = (      SELECT coalesce(sum(i.qty * i.unit_price), 0)      FROM orders o JOIN order_items i ON i.order_id = o.id      WHERE o.customer_id = r.id)    WHERE id = r.id;  END LOOP;END $$;
✓ أُنشئ الفهرس
✓ عُدِّل
✓ نُفّذت الكتلة

3000 تكرار، وفي كل تكرار استعلام تجميع وأمر تعديل.

query.sql
CREATE INDEX ON orders (customer_id);ALTER TABLE customers ADD COLUMN spent numeric; -- طريقة المجموعات: أمر واحدUPDATE customers cSET spent = coalesce(t.total, 0)FROM customers c2LEFT JOIN (  SELECT o.customer_id, sum(i.qty * i.unit_price) AS total  FROM orders o JOIN order_items i ON i.order_id = o.id  GROUP BY o.customer_id) t ON t.customer_id = c2.idWHERE c2.id = c.id;
✓ أُنشئ الفهرس
✓ عُدِّل
✓ عُدِّل 3000 صفًّا

أمر واحد يجمّع كل الطلبات مرة واحدة ثم يحدّث كل العملاء. قارن ⏱. وكلما كبر الجدول أو غاب الفهرس اتسع الفرق: بلا فهرس على customer_id تصبح الحلقة أبطأ بعشرات المرات.

٢أدوات التفكير بالمجموعات

بدل…اكتب…
حلقة فيها UPDATE لكل صفUPDATE … FROM (استعلام تجميع)
حلقة فيها INSERT لكل صفINSERT INTO t SELECT …
IF داخل الحلقة لاختيار قيمةCASE WHEN … THEN … END داخل الأمر
حلقة تحذف صفوفًا تحقق شرطًاDELETE … WHERE … أو WHERE EXISTS (…)
حلقة تبحث عن أعلى قيمة في كل مجموعةدوال النوافذ (الدرس 25)
✗ حلقة: يعمل لكنه الأسلوب الأضعف
query.sql
DO $$DECLARE r record;BEGIN  FOR r IN SELECT id, stock FROM products LOOP    IF r.stock = 0 THEN      UPDATE products SET price = price * 1.05 WHERE id = r.id;    ELSIF r.stock > 100 THEN      UPDATE products SET price = price * 0.90 WHERE id = r.id;    END IF;  END LOOP;END $$;SELECT name, stock, price FROM products WHERE stock = 0 OR stock > 100 ORDER BY id;
✓ نُفّذت الكتلة
النتيجة · 3 صفوف
namestockprice
سماعة رأس0220.5
دفتر ملاحظات20010.8
أقلام ملونة15016.2
✓ أمر واحد
query.sql
UPDATE productsSET price = price * CASE WHEN stock = 0 THEN 1.05 ELSE 0.90 ENDWHERE stock = 0 OR stock > 100; SELECT name, stock, price FROM products WHERE stock = 0 OR stock > 100 ORDER BY id;
✓ عُدِّل 3 صفوف
النتيجة · 3 صفوف
namestockprice
سماعة رأس0220.5
دفتر ملاحظات20010.8
أقلام ملونة15016.2

النتيجة نفسها، والثاني أمر واحد أقصر وأوضح وأسرع.

٣متى تبقى الحلقة مناسبة؟

الحالةلماذا الحلقة؟
معالجة على دفعات مع COMMITتقسيم ملايين الصفوف (الدرس 16)
كل صف يحتاج إجراءً خارجيًّا أو خطأً مستقلًّاالتقاط خطأ كل صف وحده (الدرس 18)
SQL ديناميكي لكل جدولأسماء جداول مختلفة في كل تكرار (الدرس 23)
منطق معقد يعتمد على نتيجة الصف السابقوإن كانت دوال النوافذ تحل كثيرًا منه

توقّع وتحقّق

لماذا الحلقة صفًّا بصف بطيئة؟

بديل IF داخل الحلقة لاختيار القيمة:

لإدخال نتيجة استعلام في جدول دفعة واحدة:

مقابل UPDATE … FROM في Oracle:

الخلاصة

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

متقدم

أضف إلى customers عمود tier واملأه حسب مجموع مشتريات العميل (كل الطلبات): ذهبي gold من 3000 فأكثر، وفضي silver من 500 فأكثر، وإلا basic. بلا حلقات.

المطلوب:
  • ALTER TABLE customers ADD COLUMN tier text;
  • أمر UPDATE واحد بـ CASE، والعملاء بلا طلبات basic.
  • لا LOOP ولا FOR في الحل.
👀 اعرض الناتج المطلوب
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء
  • تصنيف كل العملاء صحيح
  • بلا حلقات
  • CASE

💡 تلميح 1
ابدأ من مثال UPDATE … FROM في الدرس، وضع CASE مكان coalesce(t.total, 0).
💡 تلميح 2
احذف كتلة DO كاملة.

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

solution.sql
ALTER TABLE customers ADD COLUMN tier text; UPDATE customers cSET tier = CASE WHEN coalesce(t.total, 0) >= 3000 THEN 'gold'                WHEN coalesce(t.total, 0) >= 500  THEN 'silver'                ELSE 'basic' ENDFROM customers c2LEFT JOIN (  SELECT o.customer_id, sum(i.qty * i.unit_price) AS total  FROM orders o JOIN order_items i ON i.order_id = o.id  GROUP BY o.customer_id) t ON t.customer_id = c2.idWHERE c2.id = c.id;
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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