الجداول المؤقتة وCTE
الجداول المؤقتة وCTE
🌍 تشبيه من الحياة الواقعية
تخيّل أنك تطبخ طبقاً معقداً:
- استعلام فرعي — في كل مرة تحتاج "بصل أخضر مفروم"، عليك فرمه مرة أخرى (يُعاد تنفيذه في كل مرة)
- CTE — افرم البصل أولاً وضعه في وعاء، ثم استخدمه في أي وقت لاحقاً (يُسمى مرة واحدة، ويُستشهاد به عدة مرات)
- جدول مؤقت — ضع المكونات المفرومة في طبق مؤقت؛ يمكن لهذا الطبق استخدامهاだけでなく، هي متاحة طوال جلسة الطبخ بالكامل (يمكن الوصول إليها بشكل متكرر داخل الجلسة)
CTE والجداول المؤقتة كلاهما يتبعان نهج "التحضير أولاً، الاستخدام لاحقاً"، مما يجعل الاستعلامات المعقدة واضحة ومنظمة.
🎯 المفاهيم الأساسية
جملة WITH / CTE
CTE (تعبير الجدول المشترك) يستخدم كلمة WITH لتحديد مجموعة نتائج مؤقتة مسماة يمكن الاستشهاد بها مثل جدول داخل الاستعلام الحالي.
WITH cte_name AS (
-- استعلام CTE
SELECT column1, column2 FROM table1
)
-- الاستعلام الرئيسي يشير إلى CTE
SELECT * FROM cte_name;
المزايا:
- سهولة القراءة: تقسيم الاستعلامات المعقدة إلى خطوات منطقية متعددة
- إعادة الاستخدام: يمكن الاستشهاد بنفس CTE عدة مرات في الاستعلام الرئيسي
- التكرار: يدعم تجاهل هياكل الشجرة
CTE التكرارية
يتم تعريف CTE التكرارية بـ WITH RECURSIVE وتحتوي على جزأين:
- العضو المرسلي: الاستعلام الأولي، نقطة بداية التكرار
- العضو التكراري: يشير إلى نفسه، يتمدد خطوة بخطوة
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 | الاستعلام الفرعي |
|---|---|---|
| سهولة القراءة | أفضل (يُسمى أولاً، ثم يُستخدم) | يصعب قراءته عند التداخل العميق |
| إعادة الاستخدام | استشهاد متعدد | يُعاد تنفيذه في كل مرة يُستشهد به |
| التكرار | ✅ مدعوم | ❌ غير مدعوم |
| الأداء | عادةً ما يُحسَّن لنفس الخطة | نفس الشيء |
| النطاق | الجملة الحالية فقط | الجملة الحالية فقط |
الجداول المؤقتة — CREATE TEMPORARY TABLE
توجد الجداول المؤقتة داخل جلسة قاعدة البيانات الحالية ويتم حذفها تلقائياً عند انتهاء الجلسة.
CREATE TEMPORARY TABLE temp_result (
id INT,
name VARCHAR(50),
amount DECIMAL(10,2)
);
INSERT INTO temp_result SELECT ...;
SELECT * FROM temp_result;
حالات الاستخدام:
- عدة جمل SQL تتشارك نفس النتيجة الوسيطة
- بيانات وسيطة في الإجراءات المخزنة
- الحاجة إلى إنشاء فهارس على النتائج الوسيطة
ملخص حالات استخدام CTE
| السيناريو | النهج الموصى به |
|---|---|
| نتائج وسيطة في استعلامات بسيطة | استعلام فرعي |
| تقسيم الاستعلامات المعقدة إلى خطوات | CTE |
| تجاهل بيانات الشجرة/الهرمية | CTE تكرارية |
| مشاركة البيانات عبر عدة جمل SQL | جدول مؤقت |
| النتائج الوسيطة تحتاج فهارس | جدول مؤقت |
تجاهل بيانات الشجرة باستخدام WITH RECURSIVE
الحقول مثل department_id أو manager_id ذاتية الإشارة في جدول الموظفين يمكن أن تشكل علاقات هرمية. يمكن لـ CTE التكرارية تجاهلها بسهولة:
-- إيجاد جميع رؤساء الموظف (صعوداً)
-- إيجاد جميع الأقسام الفرعية تحت قسم (نزولاً)
-- إنشاء سلسلة تواريخ (سلسلة أرقام)
📝 الأساسيات
-- 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 لتقسيم استعلام معقد — تحليل مبيعات القسم للفرد الواحد (الصعوبة ⭐⭐⭐)
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;
النتيجة:
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 يشير إلى المدير المباشر:
-- أولاً أضف عمود 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;
النتيجة:
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
الشرح:
- العضو المرسلي يجد جميع الموظفين من المستوى الأعلى "بلا مدير"
- العضو التكراري يجد المرؤوسين مستوى بمستوى، مع
level + 1لتسجيل العمق REPEAT(' ', level)يستخدم المسافة البادئة لعرض الهرم بصرياًCAST(... AS CHARACTER(500))يمنع اقتطاع النصوص أثناء التكرار
🎬 تدريب على السيناريوهات
السيناريو 1: حساب قيمة العميل مدى الحياة خطوة بخطوة
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: إنشاء سلسلة تواريخ لتحليل اتجاهات المبيعات
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 | — | ✅ |
- CTE يتم تعريفها بـ
WITH، مما يحسن قابلية القراءة والصيانة للاستعلامات المعقدة - CTE التكرارية تستخدم
WITH RECURSIVE، مناسبة لتجاهل بيانات الشجرة وإنشاء السلاسل - الجداول المؤقتة يتم حذفها تلقائياً عند انتهاء الجلسة، مناسبة لمشاركة النتائج الوسيطة عبر جمل SQL
📝 تمارين
- استخدم CTE لإيجاد الموظف الأعلى راتباً في كل قسم وإظهار الفرق بين راتبه ومتوسط القسم.
- استخدم CTE تكرارية لإنشاء سلسلة أرقام من 1 إلى 20.
- استخدم جدول مؤقت لتخزين بيانات ملخص الطلبات لكل عميل، ثم قم بتحليل تصنيف العملاء بناءً على الجدول المؤقت.
- سؤال تفكير: إذا تم الاستشهاد بـ CTE مرتين من الاستعلام الرئيسي، هل سيتم تنفيذه مرتين؟ تحقق باستخدام
EXPLAIN.
الدرس التالي
👉 21-transactions - معالجة المعاملات: تعلّم خصائص ACID للمعاملات، صيغة BEGIN/COMMIT/ROLLBACK، نقاط الحفظ SAVEPOINT، مستويات عزل المعاملات، ومفهوم الجمود.



