PostgreSQL: عمليات INSERT و UPDATE و DELETE في PostgreSQL

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

تعديل البيانات (DML) هو العملية اليومية الأكثر تكرارًا — بالإضافة إلى SQL القياسي، تضيف PostgreSQL ميزتين مميزتين: RETURNING و UPSERT.

1. ما ستتعلمه


2. قصة حقيقية لشخص عمليات

(1) المشكلة: بيانات مكررة عند استيراد المنتجات بالجملة

يحتاج Charlie إلى استيراد 5000 سجل منتج بالجملة إلى قاعدة بيانات التجارة الإلكترونية، لكن بعض المنتجات موجودة بالفعل (محددة بـ name). يحتاج إلى:

(2) الحل: UPSERT + RETURNING

تتعامل INSERT ON CONFLICT (UPSERT) في PostgreSQL مع كل من الإدراج والتحديث في عبارة SQL واحدة، وتعيد جملة RETURNING الصفوف المتأثرة:

SQL
-- Upsert: إدراج منتجات جديدة، تحديث الموجودة
INSERT INTO products (name, price, stock, category)
VALUES ('Running Shoes', 99.99, 200, 'Footwear')
ON CONFLICT (name)
DO UPDATE SET
    price = EXCLUDED.price,
    stock = products.stock + EXCLUDED.stock
RETURNING id, name, price, stock,
    CASE WHEN xmax = 0 THEN 'inserted' ELSE 'updated' END AS operation;

(3) النتيجة


3. INSERT

(1) الصيغة الأساسية

SQL
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
RETURNING * | column_list;

(1) ▶ مثال

SQL
-- إدراج صف واحد
INSERT INTO users (email, name, password_hash)
VALUES ('charlie@example.com', 'Charlie', '$2a$12$hash123');

-- إدراج مع RETURNING (الحصول على id المولد تلقائيًا)
INSERT INTO users (email, name, password_hash)
VALUES ('diana@example.com', 'Diana', '$2a$12$hash456')
RETURNING id, email, created_at;

Output:

TEXT 📖 للعرض فقط
 id |       email          |          created_at
----+----------------------+-------------------------------
  5 | diana@example.com    | 2026-07-13 10:30:00+00

(2) ▶ مثال

SQL
-- إدراج صفوف متعددة في عبارة واحدة (أسرع من INSERTs متعددة)
INSERT INTO products (name, price, stock, category) VALUES
    ('Wireless Mouse', 29.99, 500, 'Accessories'),
    ('USB-C Cable', 12.99, 1000, 'Accessories'),
    ('Mechanical Keyboard', 79.99, 200, 'Input Devices'),
    ('Monitor Stand', 49.99, 150, 'Accessories'),
    ('Webcam HD', 59.99, 300, 'Video');

Output:

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

(3) ▶ مثال

SQL
-- إنشاء جدول أرشيف ونسخ البيانات من الأصل
CREATE TABLE orders_archive (LIKE orders INCLUDING ALL);

-- إدراج صفوف من استعلام SELECT
INSERT INTO orders_archive
SELECT * FROM orders
WHERE status = 'cancelled'
  AND created_at < NOW() - INTERVAL '90 days';

Output:

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

4. جملة RETURNING (ميزة PG)

RETURNING هي ميزة خاصة بـ PostgreSQL — بعد INSERT / UPDATE / DELETE تعيد الصفوف المتأثرة مباشرة، دون حاجة لـ SELECT إضافي.

(1) RETURNING عبر العمليات

العملية الصيغة تعيد
INSERT INSERT ... RETURNING * الصف المدرج حديثًا
UPDATE UPDATE ... RETURNING * الصف المحدث
DELETE DELETE ... RETURNING * الصف المحذوف
المقارنة MySQL PostgreSQL
الحصول على المعرف بعد INSERT SELECT LAST_INSERT_ID() INSERT ... RETURNING id
الحصول على الصفوف▶�لمحذوفة SELECT أولاً، ثم DELETE DELETE ... RETURNING *
الحصول على القيم بعد UPDATE UPDATE أولاً، ثم SELECT UPDATE ... RETURNING *

(4) ▶ مثال

SQL
-- INSERT: الحصول على القيم المولدة تلقائيًا فورًا
INSERT INTO users (email, name, password_hash)
VALUES ('eve@example.com', 'Eve', '$2a$12$hash789')
RETURNING id, email, created_at;

-- UPDATE: رؤية القيم قبل وبعد
UPDATE products
SET price = 69.99, stock = stock - 10
WHERE id = 1
RETURNING id, name,
    price AS new_price,
    stock AS new_stock;

-- DELETE: أرشفة قبل الحذف
WITH deleted AS (
    DELETE FROM orders
    WHERE status = 'cancelled'
      AND created_at < NOW() - INTERVAL '1 year'
    RETURNING *
)
INSERT INTO orders_archive SELECT * FROM deleted;

Output:

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

5. UPDATE

(1) الصيغة الأساسية

SQL
UPDATE table_name
SET column1 = value1, column2 = value2, ...
[WHERE condition]
[RETURNING * | column_list];

(5) ▶ مثال

SQL
-- تحديث صف واحد
UPDATE users
SET name = 'Alice Smith', updated_at = NOW()
WHERE email = 'alice@example.com';

-- تحديث بشرط
UPDATE products
SET price = price * 0.9  -- خصم 10%
WHERE category = 'Accessories' AND stock > 100;

-- تحديث مع RETURNING
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0
RETURNING id, name, stock;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(6) ▶ مثال

SQL
-- تحديث إجمالي الطلب بناءً على عناصر الطلب
UPDATE orders o
SET total_amount = (
    SELECT SUM(quantity * unit_price)
    FROM order_items oi
    WHERE oi.order_id = o.id
)
WHERE o.status = 'pending';

-- تحديث باستخدام جملة FROM (امتداد PG)
UPDATE orders o
SET total_amount = oi_sum.total
FROM (
    SELECT order_id, SUM(quantity * unit_price) AS total
    FROM order_items
    GROUP BY order_id
) oi_sum
WHERE o.id = oi_sum.order_id
  AND o.status = 'pending';

Output:

TEXT 📖 للعرض فقط
  result  
----------
   42.50
(1 row)
⚠️ ملاحظة: UPDATE بدون جملة WHERE يحدث الجدول بأكمله! هذه هي أخطر عملية شائعة. اكتب دائمًا WHERE قبل SET، كعادة.


6. DELETE

(1) الصيغة الأساسية

SQL
DELETE FROM table_name
[WHERE condition]
[RETURNING * | column_list];

(7) ▶ مثال

SQL
-- حذف صفوف محددة
DELETE FROM products
WHERE stock = 0 AND is_available = false
RETURNING id, name;

-- حذف مع استعلام فرعي
DELETE FROM orders
WHERE user_id IN (
    SELECT id FROM users WHERE is_active = false
);

-- حذف جميع الصفوف (استخدم TRUNCATE بدلاً من ذلك للجداول الكبيرة!)
-- DELETE FROM logs;  -- بطيء، يولد WAL لكل صف

Output:

TEXT 📖 للعرض فقط
DELETE 2

(2) DELETE مقابل TRUNCATE

البعد DELETE TRUNCATE
السرعة يحذف صفًا بصف، بطيء يفرغ دفعة واحدة، سريع جدًا
المعاملة يمكن التراجع عنه (داخل معاملة) يمكن أيضًا التراجع عنه (داخل معاملة)
المشغلات يشغل مشغلات مستوى الصف لا يشغل مشغلات مستوى الصف
إعادة تعيين التسلسل لا يعيد التعيين يمكن إعادة التعيين (RESTART IDENTITY)
WHERE مدعوم غير مدعوم (يفرغ الجدول بأكمله)
حالة الاستخدام حذف بعض الصفوف تفريغ الجدول بأكمله

(8) ▶ مثال

SQL
-- اقتطاع جدول واحد (سريع، يعيد تعيين التخزين)
TRUNCATE TABLE logs;

-- اقتطاع جداول متعددة دفعة واحدة
TRUNCATE TABLE order_items, orders;

-- تتالي: اقتطاع أيضًا الجداول ذات مراجع المفاتيح الخارجية
TRUNCATE TABLE users CASCADE;

-- إعادة تعيين تسلسلات الزيادة التلقائية
TRUNCATE TABLE products RESTART IDENTITY;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

7. UPSERT (INSERT ON CONFLICT)

UPSERT هي واحدة من أكثر ميزات PostgreSQL العملية تميزًا — عندما تتعارض البيانات المدرجة مع صف موجود، تقوم تلقائيًا بتحديث بدلاً من إثارة خطأ.

(1) مخطط تدفق UPSERT

100%
graph TB
    START[إدراج صف] --> CONFLICT{قيد فريد<br/>تعارض؟}
    CONFLICT -->|لا تعارض| INSERT[إدراج صف جديد<br/>RETURN المدرج]
    CONFLICT -->|تعارض!| ACTION{إجراء ON CONFLICT<br/>؟}
    ACTION -->|DO NOTHING| SKIP[تخطي هذا الصف<br/>لا خطأ، لا تحديث]
    ACTION -->|DO UPDATE| UPDATE[تحديث الصف الموجود<br/>باستخدام EXCLUDED]
    UPDATE --> RET2[RETURN الصف المحدث]

(2) الصيغة

SQL
INSERT INTO table_name (column_list)
VALUES (value_list)
ON CONFLICT (column_name | constraint_name)
DO NOTHING | DO UPDATE SET column = EXCLUDED.column ...
[RETURNING *];
الكلمة المفتاحية الوصف
ON CONFLICT (column) العمود المراد كشف التعارض عليه (يجب أن يكون له قيد UNIQUE أو مفتاح أساسي)
ON CONFLICT ON CONSTRAINT name تحديد اسم قيد محدد
DO NOTHING تخطي الصف عند التعارض (لا خطأ، لا تحديث)
DO UPDATE SET ... إجراء تحديث عند التعارض
EXCLUDED جدول افتراضي يحمل القيم التي كان من المقرر إدراجها أصلاً

(9) ▶ مثال

SQL
-- إدراج فقط إذا لم يكن البريد الإلكتروني موجودًا بالفعل
INSERT INTO users (email, name, password_hash)
VALUES ('alice@example.com', 'Alice', '$2a$12$newhash')
ON CONFLICT (email) DO NOTHING;
-- لا خطأ إذا كان البريد الإلكتروني موجودًا بالفعل

Output:

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

(10) ▶ مثال

SQL
-- Upsert: إدراج منتجات جديدة، تحديث السعر/المخزون للمنتجات الموجودة
INSERT INTO products (name, price, stock, category)
VALUES
    ('Running Shoes', 99.99, 200, 'Footwear'),
    ('USB-C Cable', 14.99, 800, 'Accessories'),
    ('New Product', 39.99, 50, 'Gadgets')
ON CONFLICT (name)
DO UPDATE SET
    price = EXCLUDED.price,
    stock = products.stock + EXCLUDED.stock,
    category = EXCLUDED.category
RETURNING id, name, price, stock;

Output:

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

(11) ▶ مثال

SQL
-- تحديث فقط إذا كان السعر الجديد أقل
INSERT INTO products (name, price, stock, category)
VALUES ('Running Shoes', 79.99, 100, 'Footwear')
ON CONFLICT (name)
DO UPDATE SET
    price = EXCLUDED.price,
    stock = EXCLUDED.stock
WHERE EXCLUDED.price < products.price;
-- يحدث فقط إذا كان السعر الجديد أرخص من السعر الحالي

Output:

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

8. DML داخل معاملة

(1) عمليات المعاملات الأساسية

الأمر الوصف
BEGIN أو START TRANSACTION بدء معاملة
COMMIT إيداع المعاملة (حفظ دائم)
ROLLBACK التراجع عن المعاملة (إلغاء جميع التغييرات)
SAVEPOINT name إنشاء نقطة حفظ
ROLLBACK TO SAVEPOINT name التراجع إلى نقطة حفظ

(12) ▶ مثال

SQL
-- بدء معاملة للاستيراد الدفعي
BEGIN;

-- إدراج طلب وعناصره كوحدة واحدة
INSERT INTO orders (user_id, total_amount, status)
VALUES (1, 0, 'pending')
RETURNING id;

-- افترض أن ما سبق أعاد id = 10
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES
    (10, 1, 2, 89.99),
    (10, 2, 5, 12.99);

-- تحديث إجمالي الطلب
UPDATE orders
SET total_amount = (2 * 89.99 + 5 * 12.99)
WHERE id = 10;

-- تحقق قبل الإيداع
SELECT * FROM orders WHERE id = 10;
SELECT * FROM order_items WHERE order_id = 10;

-- كل شيء جيد؟ إيداع
COMMIT;

-- شيء خاطئ؟ تراجع عن كل شيء
-- ROLLBACK;

Output:

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

9. مثال كامل: استيراد المنتجات بالجملة

SQL
-- ============================================
-- مثال كامل: استيراد المنتجات بالجملة مع UPSERT
-- Charlie يستورد 5000 منتج، بعضها موجود بالفعل
-- ============================================

-- الخطوة 1: إنشاء جدول استيراد مؤقت
CREATE TEMP TABLE import_products (
    name VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    stock INTEGER DEFAULT 0,
    category VARCHAR(50)
);

-- الخطوة 2: تحميل بيانات الاستيراد (محاكاة بصفوف عينة)
INSERT INTO import_products (name, price, stock, category) VALUES
    ('Running Shoes', 99.99, 200, 'Footwear'),      -- موجود بالفعل
    ('Laptop Backpack', 59.99, 300, 'Accessories'),   -- موجود بالفعل، تغير السعر
    ('Smart Water Bottle', 34.99, 500, 'Gadgets'),    -- منتج جديد
    ('Wireless Earbuds', 79.99, 400, 'Audio'),        -- منتج جديد
    ('Yoga Mat', 24.99, 600, 'Fitness');              -- منتج جديد

-- الخطوة 3: Upsert جميع بيانات الاستيراد في عبارة واحدة
BEGIN;

INSERT INTO products (name, price, stock, category)
SELECT name, price, stock, category
FROM import_products
ON CONFLICT (name)
DO UPDATE SET
    price = EXCLUDED.price,
    stock = products.stock + EXCLUDED.stock
RETURNING id, name, price, stock;

-- الخطوة 4: التحقق من النتائج
SELECT id, name, price, stock
FROM products
WHERE name IN ('Running Shoes', 'Laptop Backpack',
               'Smart Water Bottle', 'Wireless Earbuds', 'Yoga Mat')
ORDER BY name;

-- الخطوة 5: التنظيف
DROP TABLE import_products;

COMMIT;

❓ أسئلة شائعة

س ما الفرق بين INSERT ON CONFLICT و REPLACE INTO (MySQL)؟
ج REPLACE INTO في MySQL هو في الواقع DELETE + INSERT▶� يحذف الصف القديم ويدرج صفًا جديدًا، مما يغير معرف الزيادة التلقائية ويشغل المشغلات ويكسر المفاتيح الخارجية. ON CONFLICT DO UPDATE في PG هو تحديث حقيقي في المكان: يبقى المعرف كما هو ولا يتم تشغيل مشغل DELETE.
س ما هو EXCLUDED؟
ج EXCLUDED هو جدول افتراضي توفره PG في UPSERT، يحمل القيم التي كان من المقرر إدراجها لكنها فشلت في الإدراج بسبب التعارض. استخدمه للإشارة إلى القيم الجديدة، مميزًا إياها عن القيم القديمة الموجودة في الجدول. على سبيل المثال، EXCLUDED.price هو السعر الجديد و products.price هو السعر القديم.
س كم عدد الصفوف الأفضل للإدراج بالجملة؟
ج لإدراج INSERT واحد متعدد القيم، يوصى بـ 100–1000 صف. بعد 1000 صف قد تواجه مشكلات أداء تحليل SQL. للدفعات الأكبر، استخدم أمر COPY (أسرع بـ 5–10 مرات من INSERT، سيتم تناوله لاحقًا في درس النسخ الاحتياطي).
س ماذا يحدث إذا نسيت WHERE في UPDATE؟
ج يحدث الجدول بأكمله! هذا أحد أخطر أخطاء SQL. إجراءات الحماية: 1) اكتب دائمًا WHERE قبل SET؛ 2) اعمل داخل معاملة — SELECT للتأكد من النطاق أولاً، ثم UPDATE؛ 3) عيّن sql_safe_updates = on (شغّله في psql).
س هل يمكن استخدام RETURNING داخل CTE؟
ج نعم! هذا نمط PG قوي جدًا — DELETE ... RETURNING مدمج مع INSERT ... SELECT ينفذ ترحيل البيانات: WITH moved AS (DELETE FROM active WHERE ... RETURNING *) INSERT INTO archive SELECT * FROM moved.
س هل يمكن التراجع عن TRUNCATE؟
ج نعم! داخل معاملة، TRUNCATE متبوعًا بـ ROLLBACK يستعيد البيانات. هذا يختلف عن MySQL (TRUNCATE في MySQL هو DDL ولا يمكن التراجع عنه). TRUNCATE في PG آمن داخل معاملة.

📖 ملخص


📝 تمارين

  1. أساسي (★): أنشئ جدول tags (id SERIAL مفتاح أساسي، name VARCHAR(50) UNIQUE)، أدرج 5 علامات، واستخدم RETURNING لإعادة id و name لكل صف مدرج.

  2. متوسط (★★): نفذ UPSERT على جدول products من هذا الدرس — أدرج 3 صفوف منتجات، أحدها له اسم موجود بالفعل. للمنتج الموجود، حدث السعر؛ للمنتجات الجديدة، أدرج بشكل طبيعي. استخدم RETURNING لعرض نتيجة العملية.

  3. تحدي (★★★): في معاملة واحدة، أكمل ما يلي: 1) أنشئ جدول products_backup (نفس هيكل products)؛ 2) انقل المنتجات ذات السعر < 30 من products إلى products_backup باستخدام DELETE ... RETURNING + INSERT؛ 3) بعد التحقق من نتيجة الترحيل، COMMIT. اكتب سكربت SQL الك��مل.

Web-Tutorial.com

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

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

100%