طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 122 · برمجة قواعد البيانات
الدرس 18 · الوحدة 8: إنشاء إجراءات مخزنة

معالجة الأخطاء: RAISE و EXCEPTION

برنامج محترف يرفض المدخلات الخاطئة برسالة واضحة، ويتعامل مع ما يمكن التعامل معه. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تستخدم مستويات RAISE وتعرف أثر كل منها.
  • ترفع خطأ عمل برسالة وتلميح.
  • تلتقط الأخطاء بأسمائها وتقرأ SQLSTATE و SQLERRM.
  • تحدد ما يُلغى عند التقاط الخطأ وتستخدم الكتل الداخلية.

١RAISE: من رسالة إلى إيقاف

المستوىالأثر
RAISE NOTICEرسالة معلومات، والتنفيذ يستمر
RAISE WARNINGتحذير، والتنفيذ يستمر
RAISE EXCEPTIONخطأ: يوقف التنفيذ ويُلغي كل ما فعلته المعاملة

القاعدة تحمي نفسها بالقيود، لكن قواعد العمل (مثل «لا تبع أكثر من المخزون») تحتاج خطأك أنت، برسالة واضحة وتلميح:

query.sql
CREATE PROCEDURE sell(pid int, n int)LANGUAGE plpgsqlAS $$DECLARE  have int;BEGIN  SELECT stock INTO have FROM products WHERE id = pid;  IF have IS NULL THEN    RAISE EXCEPTION 'لا يوجد منتج رقمه %', pid;  ELSIF have < n THEN    RAISE EXCEPTION 'المخزون غير كافٍ: المتوفر % والمطلوب %', have, n      USING HINT = 'قلل الكمية أو انتظر التوريد', ERRCODE = 'P0001';  END IF;  UPDATE products SET stock = stock - n WHERE id = pid;END;$$; CALL sell(12, 5);
✓ أُنشئ الإجراء المخزن
✗ خطأ (السطر 18): المخزون غير كافٍ: المتوفر 3 والمطلوب 5
في الدالة sell(integer,integer)، السطر 9 داخلها (RAISE)
💡 تلميح: قلل الكمية أو انتظر التوريد

الرسالة والتلميح يظهران لمستخدم التطبيق كما كتبتهما. و P0001 هو رمز SQLSTATE العام لأخطاء RAISE.

٢التقاط الخطأ: EXCEPTION WHEN

تستطيع الكتلة أن «تلتقط» الخطأ بدل أن تتوقف، بقسم EXCEPTION في آخرها. لكل نوع خطأ اسم:

الاسممتى يقع
division_by_zeroقسمة على صفر
unique_violationقيمة مكررة
foreign_key_violationإشارة إلى صف غير موجود
check_violationمخالفة CHECK
no_data_foundSELECT INTO STRICT لم يجد صفًّا
raise_exceptionخطأ من RAISE EXCEPTION
OTHERSأي خطأ آخر
query.sql
DO $$DECLARE  msg text;  cons text;BEGIN  INSERT INTO customers (name, city, email, joined)  VALUES ('سارة', 'جدة', 'sara@mail.sa', current_date);EXCEPTION  WHEN unique_violation THEN    GET STACKED DIAGNOSTICS cons = CONSTRAINT_NAME;    RAISE NOTICE 'البريد مسجل من قبل (القيد %)', cons;  WHEN OTHERS THEN    RAISE NOTICE 'خطأ غير متوقع: % (%)', SQLERRM, SQLSTATE;END $$;
NOTICE: البريد مسجل من قبل (القيد customers_email_key)
✓ نُفّذت الكتلة

SQLSTATE رمز الخطأ، وSQLERRM رسالته، وGET STACKED DIAGNOSTICS يعطي تفاصيل أكثر مثل اسم القيد والجدول.

٣ماذا يُلغى عند الالتقاط؟

عند التقاط الخطأ يُلغى ما فعلته هذه الكتلة فقط، ويبقى ما قبلها. لذلك نضع الخطوة الخطرة في كتلة داخلية BEGIN … EXCEPTION … END:

query.sql
CREATE TABLE sales_log (msg text); DO $$BEGIN  INSERT INTO sales_log VALUES ('بدأت المعالجة');  FOR pid IN 10..12 LOOP    BEGIN                                   -- كتلة داخلية لكل منتج      UPDATE products SET stock = stock - 5 WHERE id = pid;      INSERT INTO sales_log VALUES ('بيع 5 من المنتج ' || pid);    EXCEPTION WHEN check_violation THEN      INSERT INTO sales_log VALUES ('تخطّي المنتج ' || pid || ': المخزون لا يكفي');    END;  END LOOP;END $$; SELECT * FROM sales_log;
✓ أُنشئ الجدول
✓ نُفّذت الكتلة
النتيجة · 4 صفوف
msg
بدأت المعالجة
بيع 5 من المنتج 10
بيع 5 من المنتج 11
تخطّي المنتج 12: المخزون لا يكفي

فشل المنتج 12 لم يُلغِ بيع 10 و 11، ولا سطر «بدأت المعالجة».

توقّع وتحقّق

أي مستوى يوقف التنفيذ؟

اسم خطأ القيمة المكررة:

عند التقاط الخطأ يُلغى:

مقابل RAISE EXCEPTION في Oracle:

الخلاصة

تحدٍّ برمجي: بيع آمن

متقدم

اكتب إجراء safe_sell(pid int, n int) يبيع n قطعة من المنتج بشروط عمل واضحة.

المطلوب:
  • إن كانت n أقل من 1: RAISE EXCEPTION برسالة فيها «كمية غير صالحة».
  • إن كان المنتج غير موجود: خطأ برسالة فيها «غير موجود».
  • إن كان المخزون أقل من n: خطأ برسالة فيها «غير كافٍ».
  • وإلا: أنقص المخزون بمقدار n.
👀 اعرض الناتج المطلوب
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء
  • بيع 2 من المنتج 4 ← المخزون 58
  • كمية 0 تُرفض
  • منتج غير موجود يُرفض
  • مخزون غير كافٍ يُرفض
  • رسالة «كمية غير صالحة»
  • رسالة «غير موجود»
  • رسالة «غير كافٍ»
  • RAISE EXCEPTION

💡 تلميح 1
بعد SELECT … INTO يخبرك المتغير FOUND هل وُجد صف.
💡 تلميح 2
ابدأ من الإجراء sell في الدرس وأضف فحص الكمية.

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

solution.sql
CREATE PROCEDURE safe_sell(pid int, n int)LANGUAGE plpgsqlAS $$DECLARE  have int;BEGIN  IF n < 1 THEN    RAISE EXCEPTION 'كمية غير صالحة: %', n;  END IF;  SELECT stock INTO have FROM products WHERE id = pid;  IF NOT FOUND THEN    RAISE EXCEPTION 'المنتج % غير موجود', pid;  END IF;  IF have < n THEN    RAISE EXCEPTION 'المخزون غير كافٍ: المتوفر %', have;  END IF;  UPDATE products SET stock = stock - n WHERE id = pid;END;$$;
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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