الجداول المؤقتة وCTE

الجداول المؤقتة وCTE

🌍 تشبيه من الحياة الواقعية

تخيّل أنك تطبخ طبقاً معقداً:

CTE والجداول المؤقتة كلاهما يتبعان نهج "التحضير أولاً، الاستخدام لاحقاً"، مما يجعل الاستعلامات المعقدة واضحة ومنظمة.


🎯 المفاهيم الأساسية

جملة WITH / CTE

CTE (تعبير الجدول المشترك) يستخدم كلمة WITH لتحديد مجموعة نتائج مؤقتة مسماة يمكن الاستشهاد بها مثل جدول داخل الاستعلام الحالي.

SQL
WITH cte_name AS (
    -- استعلام CTE
    SELECT column1, column2 FROM table1
)
-- الاستعلام الرئيسي يشير إلى CTE
SELECT * FROM cte_name;

المزايا:

CTE التكرارية

يتم تعريف CTE التكرارية بـ WITH RECURSIVE وتحتوي على جزأين:

  1. العضو المرسلي: الاستعلام الأولي، نقطة بداية التكرار
  2. العضو التكراري: يشير إلى نفسه، يتمدد خطوة بخطوة
SQL
WITH RECURSIVE cte_name AS (
    -- المرسلي: استعلام أولي
    SELECT ... FROM table WHERE condition
    UNION ALL
    -- التكراري: يشير إلى نفسه
    SELECT ... FROM table JOIN cte_name ON condition
)
SELECT * FROM cte_name;

حالات الاستخدام: المخططات التنظيمية، أدلة الفئات، إيجاد المسارات، وغيرها من البيانات الهرمية.

CTE مقابل الاستعلام الفرعي

الميزة CTE الاستعلام الفرعي
سهولة القراءة أفضل (يُسمى أولاً، ثم يُستخدم) يصعب قراءته عند التداخل العميق
إعادة الاستخدام استشهاد متعدد يُعاد تنفيذه في كل مرة يُستشهد به
التكرار ✅ مدعوم ❌ غير مدعوم
الأداء عادةً ما يُحسَّن لنفس الخطة نفس الشيء
النطاق الجملة الحالية فقط الجملة الحالية فقط
💡 قاعدة عامة: عندما تكون منطق الاستعلام معقداً وتحتاج إلى الاستشهاد بنفس مجموعة النتائج عدة مرات، فضل CTE.

الجداول المؤقتة — CREATE TEMPORARY TABLE

توجد الجداول المؤقتة داخل جلسة قاعدة البيانات الحالية ويتم حذفها تلقائياً عند انتهاء الجلسة.

SQL
CREATE TEMPORARY TABLE temp_result (
    id INT,
    name VARCHAR(50),
    amount DECIMAL(10,2)
);

INSERT INTO temp_result SELECT ...;
SELECT * FROM temp_result;

حالات الاستخدام:

ملخص حالات استخدام CTE

السيناريو النهج الموصى به
نتائج وسيطة في استعلامات بسيطة استعلام فرعي
تقسيم الاستعلامات المعقدة إلى خطوات CTE
تجاهل بيانات الشجرة/الهرمية CTE تكرارية
مشاركة البيانات عبر عدة جمل SQL جدول مؤقت
النتائج الوسيطة تحتاج فهارس جدول مؤقت

تجاهل بيانات الشجرة باستخدام WITH RECURSIVE

الحقول مثل department_id أو manager_id ذاتية الإشارة في جدول الموظفين يمكن أن تشكل علاقات هرمية. يمكن لـ CTE التكرارية تجاهلها بسهولة:

SQL
-- إيجاد جميع رؤساء الموظف (صعوداً)
-- إيجاد جميع الأقسام الفرعية تحت قسم (نزولاً)
-- إنشاء سلسلة تواريخ (سلسلة أرقام)

📝 الأساسيات

SQL
-- CTE أساسي
WITH cte_name AS (
    SELECT ...
)
SELECT ... FROM cte_name;

-- عدة CTEs
WITH cte1 AS (...),
     cte2 AS (...)
SELECT ... FROM cte1 JOIN cte2 ON ...;

-- CTE تكرارية
WITH RECURSIVE cte_name AS (
    -- المرسلي (نقطة البداية)
    SELECT ... FROM table WHERE initial_condition
    UNION ALL
    -- التكراري (يشير إلى نفسه)
    SELECT ... FROM table 
    JOIN cte_name ON join_condition
    WHERE termination_condition
)
SELECT * FROM cte_name;

-- جدول مؤقت
CREATE TEMPORARY TABLE temp_name AS
SELECT ... FROM ...;

-- أو إنشاء يدوي
CREATE TEMPORARY TABLE temp_name (
    col1 INT,
    col2 VARCHAR(50)
);
💡 نصيحة:

  • يمكن استخدام CTE فقط في جملة SELECT/INSERT/UPDATE/DELETE المفردة التي تليها مباشرة
  • يجب أن تحتوي CTE التكرارية على شرط إنهاء، وإلا ستحدث حلقة لا نهائية
  • لا يمكن أن تتعارض أسماء الجداول المؤقتة مع أسماء الجداول الموجودة
  • MySQL يستخدم CREATE TEMPORARY TABLE، وSQL Server يستخدم #temp_table

📌 أمثلة

مثال: استخدام CTE لتقسيم استعلام معقد — تحليل مبيعات القسم للفرد الواحد (الصعوبة ⭐⭐⭐)

SQL
WITH dept_stats AS (
    SELECT 
        d.department_id,
        d.department_name AS department,
        COUNT(DISTINCT e.employee_id) AS employee_count,
        COUNT(o.order_id) AS order_count,
        COALESCE(SUM(o.total_amount), 0) AS total_sales
    FROM departments d
    LEFT JOIN employees e ON d.department_id = e.department_id
    LEFT JOIN orders o ON e.employee_id = o.employee_id AND o.status != 'cancelled'
    GROUP BY d.department_id, d.department_name
)
SELECT 
    department,
    employee_count,
    order_count,
    total_sales,
    CASE WHEN employee_count > 0 THEN ROUND(total_sales / employee_count, 2) ELSE 0 END AS sales_per_capita,
    CASE WHEN employee_count > 0 THEN ROUND(order_count * 1.0 / employee_count, 2) ELSE 0 END AS orders_per_capita
FROM dept_stats
ORDER BY sales_per_capita DESC;
▶ جرّب الكود

النتيجة:

TEXT
department | employee_count | order_count | total_sales | sales_per_capita | orders_per_capita
-----------+----------------+-------------+-------------+------------------+------------------
Sales      |              2 |           4 |    15100.00 |          7550.00 |              2.00
Tech       |              2 |           3 |     8750.00 |          4375.00 |              1.50
Marketing  |              1 |           0 |        0.00 |             0.00 |              0.00
Finance    |              0 |           0 |        0.00 |             0.00 |              0.00

الشرح: CTE dept_stats يقوم أولاً بتجميع البيانات الأساسية، ثم يقوم الاستعلام الرئيسي بحساب مقاييس الفرد الواحد. المنطق واضح وسهل الصيانة.

مثال: CTE تكرارية — التسلسل الهرمي لإدارة الموظفين (الصعوبة ⭐⭐⭐)

افترض أن جدول employees يحتوي على حقل manager_id يشير إلى المدير المباشر:

SQL
-- أولاً أضف عمود manager_id والبيانات
ALTER TABLE employees ADD COLUMN manager_id INT;

UPDATE employees SET manager_id = NULL WHERE employee_id = 1;  -- Zhang Wei: المدير العام
UPDATE employees SET manager_id = 1    WHERE employee_id = 2;  -- Li Na -> Zhang Wei
UPDATE employees SET manager_id = 1    WHERE employee_id = 3;  -- Wang Qiang -> Zhang Wei
UPDATE employees SET manager_id = 3    WHERE employee_id = 4;  -- Zhao Min -> Wang Qiang
UPDATE employees SET manager_id = 3    WHERE employee_id = 5;  -- Liu Yang -> Wang Qiang
UPDATE employees SET manager_id = NULL WHERE employee_id = 6;  -- Chen Jing: بدون مدير

-- استعلام تكراري: بدءاً من Zhang Wei، إيجاد جميع المرؤوسين (نزولاً)
WITH RECURSIVE emp_hierarchy AS (
    -- المرسلي: نقطة البداية (Zhang Wei)
    SELECT 
        employee_id,
        CONCAT(first_name, last_name) AS name,
        manager_id,
        0 AS level,
        CAST(CONCAT(first_name, last_name) AS CHAR(500)) AS path
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- التكراري: إيجاد المرؤوسين
    SELECT 
        e.employee_id,
        CONCAT(e.first_name, e.last_name),
        e.manager_id,
        h.level + 1,
        CAST(CONCAT(h.path, ' → ', e.first_name, e.last_name) AS CHAR(500))
    FROM employees e
    INNER JOIN emp_hierarchy h ON e.manager_id = h.employee_id
)
SELECT 
    CONCAT(REPEAT('  ', level), name) AS org_chart,
    level,
    path
FROM emp_hierarchy
ORDER BY path;
▶ جرّب الكود

النتيجة:

TEXT
org_chart      | level | path
---------------+-------+----------------------------
Zhang Wei      |     0 | Zhang Wei
  Li Na        |     1 | Zhang Wei → Li Na
  Wang Qiang   |     1 | Zhang Wei → Wang Qiang
    Zhao Min   |     2 | Zhang Wei → Wang Qiang → Zhao Min
    Liu Yang   |     2 | Zhang Wei → Wang Qiang → Liu Yang
Chen Jing      |     0 | Chen Jing

الشرح:

  1. العضو المرسلي يجد جميع الموظفين من المستوى الأعلى "بلا مدير"
  2. العضو التكراري يجد المرؤوسين مستوى بمستوى، مع level + 1 لتسجيل العمق
  3. REPEAT(' ', level) يستخدم المسافة البادئة لعرض الهرم بصرياً
  4. CAST(... AS CHARACTER(500)) يمنع اقتطاع النصوص أثناء التكرار

🎬 تدريب على السيناريوهات

السيناريو 1: حساب قيمة العميل مدى الحياة خطوة بخطوة

SQL
WITH order_summary AS (
    -- الخطوة 1: تجميع بيانات الطلبات لكل عميل
    SELECT 
        customer_id,
        MIN(order_date) AS first_purchase,
        MAX(order_date) AS last_purchase,
        COUNT(order_id) AS order_count,
        SUM(total_amount) AS total_spent
    FROM orders
    WHERE status != 'cancelled'
    GROUP BY customer_id
),
customer_ltv AS (
    -- الخطوة 2: حساب أيام العمر وتردد الإنفاق
    SELECT 
        customer_id,
        first_purchase,
        last_purchase,
        order_count,
        total_spent,
        DATEDIFF(last_purchase, first_purchase) AS lifetime_days,
        CASE 
            WHEN DATEDIFF(last_purchase, first_purchase) > 0 
            THEN ROUND(total_spent / (DATEDIFF(last_purchase, first_purchase) / 30.0), 2)
            ELSE total_spent
        END AS monthly_avg_spent
    FROM order_summary
)
SELECT 
    customer_id,
    first_purchase,
    last_purchase,
    order_count,
    total_spent,
    lifetime_days,
    monthly_avg_spent,
    CASE
        WHEN monthly_avg_spent >= 3000 THEN 'قيمة عالية'
        WHEN monthly_avg_spent >= 1000 THEN 'قيمة متوسطة'
        ELSE 'قيمة منخفضة'
    END AS customer_tier
FROM customer_ltv
ORDER BY total_spent DESC;

النقطة الأساسية: عدة CTEs مترابطة، بمنطق واضح في كل خطوة — التجميع أولاً، ثم الحساب، وأخيراً التصنيف. أكثر قابلية للقراءة بكثير من الاستعلامات الفرعية المتداخلة.

السيناريو 2: إنشاء سلسلة تواريخ لتحليل اتجاهات المبيعات

SQL
WITH RECURSIVE date_series AS (
    -- المرسلي: تاريخ البداية
    SELECT CAST('2026-01-01' AS DATE) AS dt
    UNION ALL
    -- التكراري: إضافة يوم واحد في كل مرة
    SELECT DATE_ADD(dt, INTERVAL 1 DAY)
    FROM date_series
    WHERE dt < '2026-06-30'
)
SELECT 
    ds.dt AS date,
    COALESCE(COUNT(o.order_id), 0) AS order_count,
    COALESCE(SUM(o.total_amount), 0) AS sales
FROM date_series ds
LEFT JOIN orders o ON o.order_date = ds.dt AND o.status != 'cancelled'
GROUP BY ds.dt
ORDER BY ds.dt;

النقطة الأساسية: CTE تكرارية تنشئ سلسلة تواريخ مستمرة، ثم LEFT JOIN مع جدول الطلبات يضمن عرض التواريخ التي لا يوجد بها طلبات أيضاً (بقيمة 0)، مما يجعلها مناسبة لرسم مخططات الاتجاهات.


❓ أسئلة شائعة

س: ما الفرق بين CTE والجدول المؤقت؟ ج: CTE صالح فقط داخل الجملة الحالية ويختفي عند انتهاء الجملة؛ الجدول المؤقت صالح للجلسة بالكامل ويمكن الاستشهاد به بعدة جمل SQL. CTE مناسب لتقسيم استعلام معقد واحد، بينما الجداول المؤقتة مناسبة للسيناريوهات التي تحتاج إلى الوصول إلى النتائج الوسيطة عدة مرات.

س: هل يمكن لـ CTE التكرارية أن تدور بلا نهاية؟ ج: نعم، إذا لم يكن هناك شرط إنهاء مناسب. تأكد أن الجزء التكراري يحتوي على شرط WHERE لlimit العمق أو النطاق. MySQL يحتوي على معامل cte_max_recursion_depth (الافتراضي 1000) لمنع التكرار اللانهائي.

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

س: هل يمكن لعدة CTEs أن تشير إلى بعضها البعض؟ ج: نعم. عدة CTEs معرفة في نفس جملة WITH يمكن أن تشير إلى سابقاتها. ومع ذلك، لا يمكنها الإشارة المتبادلة (CTE A يشير إلى CTE B بينما CTE B يشير أيضاً إلى CTE A).


📖 ملخص

التقنية النطاق قابل لإعادة الاستخدام تكراري قابل للفهرسة
استعلام فرعي جملة واحدة
CTE جملة واحدة ✅ استشهاد متعدد
جدول مؤقت الجلسة بالكامل ✅ عدة جمل SQL

📝 تمارين

  1. استخدم CTE لإيجاد الموظف الأعلى راتباً في كل قسم وإظهار الفرق بين راتبه ومتوسط القسم.
  2. استخدم CTE تكرارية لإنشاء سلسلة أرقام من 1 إلى 20.
  3. استخدم جدول مؤقت لتخزين بيانات ملخص الطلبات لكل عميل، ثم قم بتحليل تصنيف العملاء بناءً على الجدول المؤقت.
  4. سؤال تفكير: إذا تم الاستشهاد بـ CTE مرتين من الاستعلام الرئيسي، هل سيتم تنفيذه مرتين؟ تحقق باستخدام EXPLAIN.

الدرس التالي

👉 21-transactions - معالجة المعاملات: تعلّم خصائص ACID للمعاملات، صيغة BEGIN/COMMIT/ROLLBACK، نقاط الحفظ SAVEPOINT، مستويات عزل المعاملات، ومفهوم الجمود.

Web-Tutorial.com

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

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

100%