تخطَّ إلى المحتوى

🗄️ شرح SQL وقواعد البيانات

الإدراج أو التحديث الذرّي (UPSERT)

الدرس 32 من 32· ⏱ 3 دقائق قراءة· 🗓 آخر تحديث: ٢١ يوليو ٢٠٢٦

المشكلة: تحقّق ثم إدراج أو تحديث

النمط الساذج يحتاج استعلامين منفصلين:

-- 1) تحقّق إن كان السجل موجودًا
SELECT id FROM users WHERE email = 'ali@example.com';

-- 2) بناءً على النتيجة: INSERT أو UPDATE

المشكلة أن بين الاستعلامين قد يُدرج طلب آخر نفس الصف في نفس اللحظة (Race Condition)، فتفشل عملية الإدراج بخطأ تكرار مفتاح فريد رغم أن منطق تطبيقك "تحقّق أولًا" بدا صحيحًا. الحل: أمر واحد ذرّي (Atomic) يقرّر داخل قاعدة البيانات نفسها — يُعرف بـ UPSERT (Update + Insert).

PostgreSQL: INSERT ... ON CONFLICT

INSERT INTO users (email, last_login)
VALUES ('ali@example.com', NOW())
ON CONFLICT (email) DO UPDATE
SET last_login = EXCLUDED.last_login;
  • ON CONFLICT (email) يحدّد هدف التعارض: عمود (أو أعمدة) لازم يكون عليه فهرس فريد (UNIQUE) أو مفتاح أساسي، وإلا يرفض قاعدة البيانات الأمر لعدم وجود ما يحدّد "التعارض" أصلًا.
  • EXCLUDED جدول وهمي يمثّل القيمة التي كانت ستُدرج لو ما حدث تعارض — تستخدمه في SET للإشارة للقيمة الجديدة.
  • بديل DO UPDATE هو DO NOTHING: يتجاهل الصف المكرّر بصمت بدل تحديثه.
-- تجاهل التكرار بلا أي تحديث
INSERT INTO users (email) VALUES ('ali@example.com')
ON CONFLICT (email) DO NOTHING;

يمكن أيضًا إضافة WHERE بعد SET لتحديث مشروط فقط:

INSERT INTO users (email, last_login) VALUES ('ali@example.com', NOW())
ON CONFLICT (email) DO UPDATE
SET last_login = EXCLUDED.last_login
WHERE users.last_login < EXCLUDED.last_login;

MySQL: INSERT ... ON DUPLICATE KEY UPDATE

INSERT INTO users (email, last_login)
VALUES ('ali@example.com', NOW())
ON DUPLICATE KEY UPDATE last_login = VALUES(last_login);

يعمل عند مخالفة أي مفتاح أساسي أو فهرس فريد موجود على الجدول — لا حاجة لتحديد عمود التعارض صراحة كما في PostgreSQL، لأن MySQL يفحص كل القيود الفريدة تلقائيًا.

⚠️ الدالة VALUES() هنا للإشارة إلى القيمة المقترح إدراجها — MySQL 8.0.19 فما بعد أضاف صياغة بديلة أوضح بأسماء مستعارة (AS new) لأن VALUES() أصبحت قديمة (Deprecated) ومرشّحة للإزالة مستقبلًا:

INSERT INTO users (email, last_login) VALUES ('ali@example.com', NOW()) AS new
ON DUPLICATE KEY UPDATE last_login = new.last_login;

جدول مقارنة سريع

PostgreSQLMySQL
الجملةON CONFLICT (col) DO UPDATEON DUPLICATE KEY UPDATE
تحديد عمود التعارضإلزامي مع DO UPDATEغير مطلوب — تلقائي من القيود الفريدة
الإشارة للقيمة الجديدةEXCLUDED.columnnew.column (أو VALUES(column) القديمة)
تجاهل بلا تحديثDO NOTHINGINSERT IGNORE (أمر منفصل)

💡 UPSERT مفيد جدًا لجداول العدّادات والإحصائيات (زيارة صفحة، إعجاب) حيث تريد "أنشئ الصف إن لم يوجد، وإلا زد القيمة" في أمر واحد بلا شرط سباق.

-- عدّاد زيارات ذرّي بـ PostgreSQL
INSERT INTO page_views (page_id, views) VALUES (42, 1)
ON CONFLICT (page_id) DO UPDATE
SET views = page_views.views + 1;

🎯 بذلك أكملت حزمة الاستعلامات المتقدّمة (CTE، دوال النافذة، وUPSERT) — راجع خلاصة المسار وطبّقها في مشروع حقيقي.

شرح الإدراج أو التحديث الذرّي (UPSERT) — SQL وقواعد البيانات بالعربي
الإدراج أو التحديث الذرّي (UPSERT)SQL وقواعد البيانات بالعربي · The Code Fix

📚 لمزيد من التعمّق في SQL وقواعد البيانات، راجِع توثيق SQL في PostgreSQL.

هل كان هذا الدرس مفيدًا؟