PostgreSQL: الإجراءات المخزنة و PL/pgSQL في PostgreSQL

آخر تحديث: 2026-08-26

1. ما ستتعلمه


2. القصة

Bob هو مهندس خلفية في منصة تجارة إلكترونية. كل صباح يحتاج إلى تشغيل سلسلة من مهام البيانات تلقائيًا:

  1. حساب ملخص مبيعات اليوم السابق
  2. تحديث طريقة العرض المادية mv_daily_sales
  3. إذا فشل الحساب، إخطار فريق العمليات

قرر Bob كتابة إجراء مخزن sp_daily_sales_refresh() بـ PL/pgSQL، مغلفًا كل المنطق على جانب قاعدة البيانات "للتنفيذ بنقرة واحدة."


3. المفهوم: FUNCTION مقابل PROCEDURE

(1) الفرق بين الدوال والإجراءات في PG

الميزة FUNCTION PROCEDURE
قيمة الإرجاع يجب أن يكون لها RETURNS لا RETURNS (قد لا ترجع شيئًا)
أسلوب الاستدعاء SELECT func() CALL proc()
التحكم بالمعاملات لا يمكن COMMIT/ROLLBACK داخليًا يمكن COMMIT/ROLLBACK داخليًا
الاستخدام في SQL في SELECT/WHERE لا يمكن تضمينها في SQL
معاملات INOUT مدعومة، تصبح تلقائيًا أعمدة إرجاع مدعومة، تمرر مرة أخرى عبر CALL

(1) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_get_order_count(p_customer_id INT)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
  v_count INT;
BEGIN
  SELECT COUNT(*) INTO v_count
  FROM orders
  WHERE customer_id = p_customer_id;
  RETURN v_count;
END;
$$;

-- استدعاء كقيمة عددية في SELECT
SELECT fn_get_order_count(1001);

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(2) ▶ مثال

SQL
CREATE OR REPLACE PROCEDURE sp_reset_daily_stats()
LANGUAGE plpgsql
AS $$
BEGIN
  TRUNCATE TABLE daily_stats;
  INSERT INTO daily_stats (stat_date, total_orders, total_revenue)
  VALUES (CURRENT_DATE, 0, 0.00);
  COMMIT;
END;
$$;

-- استدعاء بـ CALL
CALL sp_reset_daily_stats();

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

(2) متى تستخدم FUNCTION مقابل PROCEDURE

السيناريو الموصى به السبب
حساب وإرجاع قيمة FUNCTION يمكن تضمينها في SQL
عمليات ETL دفعة PROCEDURE تدعم التحكم الداخلي بالمعاملات
استدعاء مشغل FUNCTION المشغلات تقبل الدوال فقط
مهمة مجدولة PROCEDURE يمكن الإيداع خطوة بخطوة

(3) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_calc_tax(
  p_amount NUMERIC,
  p_rate NUMERIC DEFAULT 0.08
)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN ROUND(p_amount * p_rate, 2);
END;
$$;

SELECT fn_calc_tax(1000);        -- استخدام الافتراضي 8%
SELECT fn_calc_tax(1000, 0.10);  -- مخصص 10%

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

4. المفهوم: أساسيات PL/pgSQL

(1) المتغيرات والإسناد

SQL
DECLARE
  v_name TEXT;
  v_price NUMERIC(10,2) := 0.00;
  v_count INT;
  v_row orders%ROWTYPE;       -- نوع صف من الجدول
  v_status TEXT NOT NULL := 'pending';
BEGIN
  v_name := 'Bob';
  SELECT unit_price INTO v_price FROM products WHERE product_id = 1;
END;
أسلوب التصريح الصيغة الوصف
نوع أساسي v_name TEXT; تصريح مباشر
قيمة افتراضية v_price NUMERIC := 0; := أو DEFAULT
نوع صف v_row orders%ROWTYPE; يطابق هيكل الجدول
NOT NULL v_status TEXT NOT NULL := 'x'; يجب تعيين قيمة ابتدائية

(4) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_customer_summary(p_id INT)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
  v_name TEXT;
  v_total NUMERIC(12,2);
  v_msg TEXT;
BEGIN
  SELECT first_name || ' ' || last_name, SUM(total_amount)
    INTO v_name, v_total
  FROM customers c
  LEFT JOIN orders o ON o.customer_id = c.customer_id
  WHERE c.customer_id = p_id
  GROUP BY c.customer_id, c.first_name, c.last_name;

  v_msg := v_name || ': $' || COALESCE(v_total::TEXT, '0');
  RETURN v_msg;
END;
$$;

Output:

TEXT 📖 للعرض فقط
  result  
----------
   42.50
(1 row)

(2) العبارات الشرطية

(5) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_discount_tier(p_total NUMERIC)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
  IF p_total >= 10000 THEN
    RETURN 'PLATINUM';
  ELSIF p_total >= 5000 THEN
    RETURN 'GOLD';
  ELSIF p_total >= 1000 THEN
    RETURN 'SILVER';
  ELSE
    RETURN 'BRONZE';
  END IF;
END;
$$;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(6) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_order_priority(p_amount NUMERIC)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
  CASE
    WHEN p_amount >= 5000 THEN RETURN 'URGENT';
    WHEN p_amount >= 1000 THEN RETURN 'HIGH';
    WHEN p_amount >= 100  THEN RETURN 'NORMAL';
    ELSE RETURN 'LOW';
  END CASE;
END;
$$;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(3) عبارات الحلقة

نوع الحلقة الصيغة الأفضل لـ
LOOP LOOP ... END LOOP; يحتاج EXIT يدوي
WHILE WHILE cond LOOP ... END LOOP; فحص الشرط أولاً
FOR (عدد صحيح) FOR i IN 1..10 LOOP ... END LOOP; عدد ثابت من التكرارات
FOR (استعلام) FOR rec IN SELECT ... LOOP ... END LOOP; التكرار على نتائج الاستعلام

(7) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_find_price_threshold(
  p_target NUMERIC
)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
  v_limit INT := 10;
  v_sum NUMERIC := 0;
  v_id INT;
BEGIN
  LOOP
    SELECT unit_price INTO v_sum FROM products
    WHERE product_id = v_limit;
    v_sum := v_sum + p_target;
    EXIT WHEN v_limit > 100 OR v_sum > p_target * 10;
    v_limit := v_limit + 10;
  END LOOP;
  RETURN v_limit;
END;
$$;

Output:

TEXT 📖 للعرض فقط
  result  
----------
   42.50
(1 row)

(8) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_sum_category_revenue()
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
DECLARE
  v_rec RECORD;
  v_total NUMERIC(14,2) := 0;
BEGIN
  FOR v_rec IN
    SELECT category, SUM(unit_price * stock_qty) AS cat_rev
    FROM products
    GROUP BY category
  LOOP
    v_total := v_total + v_rec.cat_rev;
    RAISE NOTICE 'Category %: $%', v_rec.category, v_rec.cat_rev;
  END LOOP;
  RETURN v_total;
END;
$$;

Output:

TEXT 📖 للعرض فقط
  result  
----------
   42.50
(1 row)

5. المفهوم: أنماط المعاملات

(1) IN / OUT / INOUT / VARIADIC

النمط إدخال إخراج الوصف
IN (افتراضي) نعم لا معامل للقراءة فقط
OUT لا نعم معامل إخراج، يصبح تلقائيًا عمود إرجاع
INOUT نعم نعم معامل ثنائي الاتجاه
VARIADIC نعم لا قائمة وسائط متغيرة الطول (مصفوفة)

(9) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_order_stats(
  p_customer_id INT,
  OUT o_count INT,
  OUT o_total NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
  SELECT COUNT(*), COALESCE(SUM(total_amount), 0)
    INTO o_count, o_total
  FROM orders
  WHERE customer_id = p_customer_id;
END;
$$;

SELECT * FROM fn_order_stats(1001);

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(10) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_apply_discount(
  p_price INOUT NUMERIC,
  p_rate NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
  p_price := ROUND(p_price * (1 - p_rate), 2);
END;
$$;

-- يرجع p_price المعدل كنتيجة
SELECT fn_apply_discount(99.99, 0.15);

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(2) RETURN مقابل RETURN QUERY

العبارة الغرض نوع الإرجاع
RETURN value; إرجاع قيمة واحدة يطابق نوع RETURNS
RETURN NEXT row; إلحاق صف واحد بمجموعة النتائج RETURNS SETOF
RETURN QUERY SELECT ...; إرجاع نتيجة استعلام كاملة RETURNS SETOF/TABLE

(11) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_orders_by_date(p_date DATE)
RETURNS SETOF orders
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
    SELECT * FROM orders
    WHERE order_date = p_date
    ORDER BY order_id;
END;
$$;

SELECT * FROM fn_orders_by_date('2025-06-01');

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(12) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_top_customers(p_limit INT)
RETURNS TABLE(
  customer_name TEXT,
  total_spent NUMERIC,
  order_count INT
)
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
    SELECT
      c.first_name || ' ' || c.last_name,
      SUM(o.total_amount)::NUMERIC,
      COUNT(*)::INT
    FROM customers c
    JOIN orders o ON o.customer_id = c.customer_id
    GROUP BY c.customer_id, c.first_name, c.last_name
    ORDER BY total_spent DESC
    LIMIT p_limit;
END;
$$;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

6. المفهوم: المؤشرات

(1) المؤشرات الصريحة و REFCURSOR

نوع المؤشر التصريح الأفضل لـ
مؤشر مرتبط CURSOR (query) FOR استعلام ثابت
REFCURSOR REFCURSOR استعلام ديناميكي، يمكن إرجاعه للمستدعي
مؤشر ضمني FOR rec IN SELECT تنقل بسيط

(13) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_process_overdue_orders()
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
  v_cur CURSOR(p_date DATE) FOR
    SELECT order_id, customer_id, total_amount
    FROM orders
    WHERE order_date < p_date
      AND order_status = 'pending';
  v_rec RECORD;
  v_count INT := 0;
BEGIN
  OPEN v_cur(CURRENT_DATE - INTERVAL '7 days');
  LOOP
    FETCH v_cur INTO v_rec;
    EXIT WHEN NOT FOUND;
    UPDATE orders SET order_status = 'cancelled'
    WHERE order_id = v_rec.order_id;
    v_count := v_count + 1;
  END LOOP;
  CLOSE v_cur;
  RETURN v_count;
END;
$$;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(14) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_open_category_cursor(
  p_category TEXT
)
RETURNS REFCURSOR
LANGUAGE plpgsql
AS $$
DECLARE
  v_ref REFCURSOR;
BEGIN
  OPEN v_ref FOR
    SELECT product_id, product_name, unit_price
    FROM products
    WHERE category = p_category
    ORDER BY unit_price DESC;
  RETURN v_ref;
END;
$$;

BEGIN;
SELECT fn_open_category_cursor('Electronics');
-- يرجع اسم المؤشر مثل "<unnamed portal 1>"
FETCH ALL FROM "<unnamed portal 1>";
COMMIT;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

7. المفهوم: معالجة الاستثناءات

(1) كتلة EXCEPTION

SQL
BEGIN
  -- المنطق العادي
EXCEPTION
  WHEN OTHERS THEN
    -- معالجة الخطأ
END;
الحالة الوصف
NO_DATA_FOUND SELECT INTO لم يرجع أي صفوف
TOO_MANY_ROWS SELECT INTO أرجع أكثر من صف واحد
UNIQUE_VIOLATION انتهاك قيد التفرد
FOREIGN_KEY_VIOLATION انتهاك مفتاح خارجي
DIVISION_BY_ZERO قسمة على صفر
OTHERS التقاط جميع الاستثناءات

(15) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_safe_insert_product(
  p_name TEXT, p_price NUMERIC, p_cat TEXT
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO products (product_name, unit_price, category)
  VALUES (p_name, p_price, p_cat);
  RETURN 'OK';
EXCEPTION
  WHEN unique_violation THEN
    RETURN 'DUPLICATE: ' || p_name;
END;
$$;

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

(2) RAISE إشعار وخطأ

مستوى RAISE السلوك
DEBUG سجل تطوير فقط
LOG مكتوب إلى سجل الخادم
NOTICE معروض للعميل
WARNING معروض للعميل + سجل
EXCEPTION يرمي خطأ، يتراجع عن المعاملة

(16) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_validate_order(p_amount NUMERIC)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
  IF p_amount <= 0 THEN
    RAISE EXCEPTION 'Invalid order amount: $%', p_amount;
  ELSIF p_amount > 100000 THEN
    RAISE WARNING 'Large order: $% - requires approval', p_amount;
  ELSE
    RAISE NOTICE 'Order validated: $%', p_amount;
  END IF;
  RETURN true;
END;
$$;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

8. المفهوم: SQL الديناميكي

(1) استخدام EXECUTE

السيناريو الصيغة الوصف
DDL ديناميكي EXECUTE 'CREATE TABLE ...'; بناء SQL في وقت التشغيل
معلمي EXECUTE fmt USING v1, v2; معلمي، آمن من الحقن
الحصول على نتيجة EXECUTE sql INTO v_var; تخزين في متغير
الحصول عل�� عدة صفوف FOR rec IN EXECUTE sql LOOP التكرار على استعلام ديناميكي

(17) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_create_monthly_partition(
  p_year INT, p_month INT
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
  v_sql TEXT;
  v_table TEXT;
  v_start DATE;
  v_end DATE;
BEGIN
  v_table := 'orders_y' || p_year || 'm' || LPAD(p_month::TEXT, 2, '0');
  v_start := make_date(p_year, p_month, 1);
  v_end := v_start + INTERVAL '1 month';

  v_sql := format(
    'CREATE TABLE IF NOT EXISTS %I PARTITION OF orders
     FOR VALUES FROM (%L) TO (%L)',
    v_table, v_start, v_end
  );
  EXECUTE v_sql;
  RETURN v_table || ' created';
END;
$$;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(18) ▶ مثال

SQL
CREATE OR REPLACE FUNCTION fn_search_products(
  p_col TEXT, p_val TEXT
)
RETURNS SETOF products
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY EXECUTE format(
    'SELECT * FROM products WHERE %I = $1',
    p_col
  ) USING p_val;
END;
$$;

SELECT * FROM fn_search_products('category', 'Electronics');

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

9. مخطط التدفق: قرارات هيكل PL/pgSQL

100%
flowchart TD
    A[هل تحتاج تغليف المنطق؟] --> B{هل تحتاج قيمة إرجاع؟}
    B -->|نعم| C[CREATE FUNCTION]
    B -->|لا| D{هل تحتاج تحكم بالمعاملات؟}
    D -->|نعم| E[CREATE PROCEDURE]
    D -->|لا| C
    C --> F{إرجاع صف واحد / عدة صفوف؟}
    F -->|قيمة واحدة| G[RETURN value]
    F -->|عدة صفوف| H{هل الاستعلام ثابت؟}
    H -->|نعم| I[RETURN QUERY SELECT]
    H -->|لا| J[FOR rec IN EXECUTE ... LOOP]
    E --> K{هل تحتاج SQL ديناميكي؟}
    K -->|نعم| L[EXECUTE format(...) USING]
    K -->|لا| M[عبارات SQL ثابتة]
    G --> N{هل يمكن أن يخطئ؟}
    N -->|نعم| O[كتلة EXCEPTION]
    N -->|لا| P[منطق مباشر]
    I --> N

    style A fill:#e1f5fe
    style O fill:#ffcdd2
    style L fill:#c8e6c9

10. مثال شامل

الإجراء المخزن اليومي لملخص المبيعات لـ Bob — يحسب المبيعات، يحدث طريقة العرض المادية، ويخطر بالأخطاء:

SQL
CREATE OR REPLACE PROCEDURE sp_daily_sales_refresh()
LANGUAGE plpgsql
AS $$
DECLARE
  v_yesterday DATE := CURRENT_DATE - INTERVAL '1 day';
  v_order_count INT;
  v_total_revenue NUMERIC(14,2);
  v_avg_order NUMERIC(10,2);
BEGIN
  RAISE NOTICE 'بدء التحديث اليومي لـ %', v_yesterday;

  SELECT COUNT(*), COALESCE(SUM(total_amount), 0),
         COALESCE(AVG(total_amount), 0)
    INTO v_order_count, v_total_revenue, v_avg_order
  FROM orders
  WHERE order_date = v_yesterday
    AND order_status = 'completed';

  INSERT INTO daily_sales_summary
    (stat_date, order_count, total_revenue, avg_order_value, created_at)
  VALUES
    (v_yesterday, v_order_count, v_total_revenue, v_avg_order, NOW())
  ON CONFLICT (stat_date) DO UPDATE
    SET order_count = EXCLUDED.order_count,
        total_revenue = EXCLUDED.total_revenue,
        avg_order_value = EXCLUDED.avg_order_value;

  REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_sales;

  RAISE NOTICE 'تم: % طلب، $% إيرادات، $% متوسط',
    v_order_count, v_total_revenue, v_avg_order;

EXCEPTION
  WHEN OTHERS THEN
    INSERT INTO error_log (error_time, procedure_name, error_msg)
    VALUES (NOW(), 'sp_daily_sales_refresh', SQLERRM);
    RAISE NOTICE 'خطأ في التحديث اليومي: %', SQLERRM;
    -- إخطار فريق العمليات عبر pg_notify
    PERFORM pg_notify('ops_alerts',
      'فشل تحديث المبيعا�� اليومي: ' || SQLERRM);
END;
$$;

❓ أسئلة شائعة

س ما هو أهم فرق بين FUNCTION و PROCEDURE؟
ج يمكن لـ PROCEDURE تنفيذ COMMIT/ROLLBACK داخليًا (معاملات مستقلة)، بينما FUNCTION لا يمكنه ذلك. يجب أن يكون لـ FUNCTION عبارة RETURNS ويمكن استدعاؤه داخل SQL، بينما PROCEDURE يستدعى بـ CALL.
س ماذا يحدث عندما لا يجد SELECT INTO صفًا مطابقًا؟
ج يحتفظ المتغير بقيمته الأصلية (لا يتم تعيينه إلى NULL). للكشف عن "لا بيانات"، استخدم IF NOT FOUND THEN أو EXCEPTION WHEN NO_DATA_FOUND.
س ما الفرق بين RETURN NEXT و RETURN QUERY؟
ج RETURN NEXT يلحق صفًا واحدًا بمجموعة النتائج في كل مرة ويجب ا��تخدامه مع حلقة؛ RETURN QUERY يرجع نتيجة SELECT كاملة مرة واحدة وهو أكثر إيجازًا. كلاهما يتطلب RETURNS SETOF.
س لماذا يوصى بـ format + USING على ربط السلاسل لـ SQL الديناميكي؟
ج %I في format يقتبس المعرفات تلقائيًا و %L يتعامل مع القيم الحرفية، بينما USING يعلمن التنفيذ لمنع حقن SQL. ربط السلاسل المباشر يحمل خطر الحقن ويجبرك على التعامل مع هروب علامات الاقتباس يدويًا.
س هل تؤثر كتلة EXCEPTION على الأداء؟
ج نعم. كتلة EXCEPTION تنشئ نقطة حفظ معاملة فرعية، مما يضيف عبئًا. الدوال ذات المسار الساخن المستدعاة بشكل متكرر يجب أن تتجنب كتل EXCEPTION غير الضرورية.
س متى يكون مؤشر REFCURSOR مفيدًا؟
ج عندما تحتاج إلى تأجيل جلب نتيجة الاستعلام إلى المستدعي — مثلاً، طبقة التطبيق تجلب مجموعة بيانات كبيرة على دفعات، أو إجراء يرجع مؤشرًا ليقرر المستدعي كيفية التكرار. لاحظ أن المؤشرات يجب استخدامها داخل معاملة.
س كيف أداء حلقة FOR rec IN SELECT في PL/pgSQL؟
ج إنها مكافئة لتنقل مؤشر جانب الخادم وأكثر كفاءة بكثير من جلب الصفوف واحدًا تلو الآخر في طبقة التطبيق. لكن لعمليات البيانات الكبيرة، فضل SQL المعتمد على المجموعات (INSERT/UPDATE ... SELECT)؛ استخدم الحلقات فقط للمنطق الذي لا يمكن لـ SQL المعتمد على المجموعات التعبير عنه.
س هل سيرى العملاء دائمًا رسائل RAISE NOTICE؟
ج يعتمد على تكوين العميل. psql يعرض NOTICE افتراضيًا، لكن العديد من ORMs والمشغلات تتجاهلها افتراضيًا. استخدم RAISE WARNING أو اضبط log_min_messages للتحكم في هذا.

📖 ملخص


📝 تمارين

  1. ⭐ اكتب دالة fn_get_product_price(p_id INT) تستخدم SELECT INTO للاستعلام عن unit_price من جدول products وترجع 0 إذا لم يوجد صف.

  2. ⭐⭐ اكتب دالة fn_customer_tier(p_customer_id INT) تستعلم عن إجمالي إنفاق العميل وترجع فئة PLATINUM/GOLD/SILVER/BRONZE باستخدام IF/ELSIF (العتبات: 10000 / 5000 / 1000 دولار).

  3. ⭐⭐⭐ اكتب إجراء مخزن sp_monthly_revenue_report(p_year INT, p_month INT): استعلم ديناميكيًا عن ملخص طلبات ذلك الشهر، INSERT في جدول monthly_reports، التقط الأخطاء بـ EXCEPTION واكتبها إلى error_log، أخرج التقدم بـ RAISE NOTICE، وابنِ اسم الجدول ديناميكيًا بـ format (مثلاً، orders_y2025m06).

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%