PostgreSQL: قيود PostgreSQL وسلامة البيانات
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- PRIMARY KEY (عمود واحد / مركب)
- FOREIGN KEY (CASCADE / SET NULL / SET DEFAULT / NO ACTION / RESTRICT)
- قيد UNIQUE
- قيد NOT NULL
- قيد CHECK (ميزة PostgreSQL: يمكنه الإشارة إلى أعمدة أخرى في نفس الصف)
- قيد EXCLUSION (ميزة PostgreSQL: مثلاً، نطاقات زمنية غير متداخلة)
- قيد DEFERRABLE المؤجل (ميزة PostgreSQL)
- تسمية القيود وإدارتها
2. القصة
Charlie هو مهندس قاعدة بيانات في منصة SaaS. يحتاج إلى فرض قواعد العمل التالية على مستوى قاعدة البيانات:
- يجب أن يكون مبلغ كل طلب > 0 (CHECK)
- يجب ألا تتداخل النطاقات الزمنية لحجوزات غرف الاجتماعات (EXCLUSION)
- يجب أن يؤدي حذف عميل إلى تنظيف طلباته تلقائيًا، لكن يجب حماية الطلبات الحرجة (استراتيجية تعاقب FOREIGN KEY)
- عند استيراد البيانات بكميات كبيرة، قد تنتهك الإشارات بين الصفوف القيود مؤقتًا، ويجب التحقق منها فقط بعد اكتمال الاستيراد (DEFERRABLE)
يستخدم Charlie القيود بدلاً من التحقق على مستوى التطبيق، مما يجعل قاعدة البيانات خط الدفاع الأخير لسلامة البيانات.
3. المفهوم: PRIMARY KEY
(1) دور المفتاح الأساسي
يحدد المفتاح الأساسي كل صف بشكل فريد في الجدول، ويجمع بين UNIQUE + NOT NULL. يمكن أن يكون للجدول مفتاح أساسي واحد فقط.
| الخاصية | الوصف |
|---|---|
| التفرد | لا يُسمح بالقيم المكررة |
| غير فارغ | لا يُسمح بـ NULL |
| فهرس تلقائ�� | يقوم PostgreSQL تلقائيًا بإنشاء فهرس B-Tree فريد للمفتاح الأساسي |
| واحد لكل جدول | يمكن تعريف PRIMARY KEY واحد فقط |
(1) ▶ مثال
CREATE TABLE customers (
customer_id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE
);
Output:
CREATE TABLE
(2) ▶ مثال
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
PRIMARY KEY (order_id, product_id)
);
Output:
CREATE TABLE
(2) مقارنة أنواع أعمدة المفتاح الأساسي
| النوع | التخزين | النطاق | حالة الاستخدام |
|---|---|---|---|
| SERIAL / BIGSERIAL | 4/8 بايت | 2 مليار / 9.2×10¹⁸ | معظم جداول الأعمال |
| UUID | 16 بايت | فريد عالميًا | الأنظمة الموزعة |
| مفتاح طبيع�� (مثلاً: البريد الإلكتروني) | متغير | — | نادر الاستخدام؛ خطر تغيير الأعمال |
| مفتاح أساسي مركب | عدة أعمدة | — | جداول الوصل، جداول جسر كثير إلى كثير |
(3) ▶ مثال
CREATE TABLE global_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_name VARCHAR(200) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Output:
CREATE TABLE
4. المفهوم: FOREIGN KEY
(1) المفتاح الخارجي واستراتيجيات التعاقب الخمس
يضمن المفتاح الخارجي سلامة المرجعية: يجب أن تكون قيمة العمود المرجع في الجدول الابن موجودة في المفتاح الأساسي/الفريد للجدول الأب.
| الاستراتيجية | سلوك ON DELETE | سلوك ON UPDATE | السيناريو النموذجي |
|---|---|---|---|
| CASCADE | حذف تعاقبي للصفوف الابنة | تحديث تعاقبي للصفوف الابنة | حذف عناصر الطلب مع الطلب |
| SET NULL | تعيين الصف الابن إلى NULL | تعيين الصف الابن إلى NULL | ارتباط اختياري؛ الحذف ليس له تأثير |
| SET DEFAULT | تعيين الصف الابن إلى القيمة الافتراضية | تعيين الصف الابن إلى القيمة الافتراضية | نادر الاستخدام |
| RESTRICT | رفض الحذف (فوري) | رفض التحديث (فوري) | حماية البيانات الحرجة |
| NO ACTION | رفض الحذف (قابل للتأجيل) | رفض التحديث (قابل للتأجيل) | السلوك الافتراضي |
(4) ▶ مثال
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL
REFERENCES customers(customer_id) ON DELETE CASCADE,
total_amount DECIMAL(12,2) NOT NULL
);
Output:
CREATE TABLE
عند حذف عميل، يتم حذف جميع طلباته تلقائيًا.
(5) ▶ مثال
CREATE TABLE invoices (
invoice_id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL
REFERENCES orders(order_id) ON DELETE RESTRICT,
invoice_date DATE NOT NULL
);
Output:
CREATE TABLE
إذا كان الطلب لديه فاتورة بالفعل، يتم رفض حذف الطلب.
(2) مقارنة CASCADE مقابل RESTRICT
| البعد | CASCADE | RESTRICT |
|---|---|---|
| حذف الصف الأب | حذف الصفوف الابنة أيضًا | رفض الحذف |
| أمان البيانا�� | مريح لكنه محفوف بالمخاطر | آمن لكنه يحتاج تنظيفًا يدويًا |
| حالة الاستخدام | البيانات المساعدة | البيانات الحرجة / المالية |
(6) ▶ مثال
CREATE TABLE customers (
customer_id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
customer_id BIGINT REFERENCES customers(customer_id) ON DELETE CASCADE
);
CREATE TABLE order_items (
item_id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(order_id) ON DELETE CASCADE,
product_id BIGINT NOT NULL
);
Output:
CREATE TABLE
حذف عميل → حذف تلقائي للطلبات → حذف تلقائي لعناصر الطلب، تعاقب ثلاثي المستويات.
(7) ▶ مثال
CREATE TABLE reviews (
review_id BIGSERIAL PRIMARY KEY,
product_id BIGINT REFERENCES products(product_id) ON DELETE SET NULL,
content TEXT NOT NULL
);
Output:
CREATE TABLE
بعد حذف منتج، يتم تعيين product_id في المراجعة إلى NULL، لكن يتم الاحتفاظ بالمراجعة.
5. المفهوم: UNIQUE و NOT NULL
(1) قيد UNIQUE
يضمن UNIQUE عدم تكرار قيم العمود (أو تركيبات الأعمدة)، ويسمح بـ NULL (عدة قيم NULL لا تعتبر تكرارًا).
| البعد | PRIMARY KEY | UNIQUE |
|---|---|---|
| العدد لكل جدول | واحد فقط | مسموح بعدة |
| NULL مسموح | غير مسموح | مسموح (عدة NULL لا تتعارض) |
| فهرس تلقائي | نعم | نعم |
| الدلالة | يحدد الصف | يضمن التفرد |
(8) ▶ مثال
CREATE TABLE user_accounts (
user_id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE,
phone VARCHAR(20),
UNIQUE (email, phone)
);
Output:
CREATE TABLE
البريد الإلكتروني فريد بمفرده + (البريد الإلكتروني، الهاتف) فريد كتركيبة.
(2) قيد NOT NULL
يمنع NOT NULL تخزين قيم NULL في عمود؛ إنه أبسط قيد س��امة.
| الكتابة | الوصف |
|---|---|
col TYPE NOT NULL |
قيد على مستوى العمود |
CONSTRAINT nn_col CHECK (col IS NOT NULL) |
شكل مكافئ |
(9) ▶ مثال
CREATE TABLE products (
product_id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
description TEXT
);
Output:
CREATE TABLE
description يسمح بـ NULL؛ الأعمدة الرئيسية الأخرى لا تسمح.
6. المفهوم: قيد CHECK
(1) أساسيات قيد CHECK
يتطلب قيد CHECK أن تحقق قيمة العمود تعبيرًا منطقيًا معينًا. يمكن لـ CHECK في PostgreSQL الإشارة إلى أعمدة أخرى في نفس الصف (يدعم معيار SQL هذا أيضًا، لكن العديد من قواعد البيانات لا تدعمه).
| الخاصية | الوصف |
|---|---|
| فحص على مستوى الصف | يمكنه الإشارة فقط إلى أعمدة الصف الحالي |
| ميزة PostgreSQL | يمكنه الإشارة إلى صفوف أخرى (عبر استعلام فرعي، مع قيود) |
| CHECK على مستوى الجدول | يمكنه تقييد عدة أعمدة مرة واحدة |
| NO INHERIT | لا ينتشر إلى الجداول الابنة (ميزة PG) |
(10) ▶ مثال
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
total_amount DECIMAL(12,2) NOT NULL CHECK (total_amount > 0),
discount DECIMAL(12,2) DEFAULT 0 CHECK (discount >= 0)
);
Output:
CREATE TABLE
(11) ▶ مثال
CREATE TABLE campaigns (
campaign_id BIGSERIAL PRIMARY KEY,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
budget DECIMAL(12,2) NOT NULL CHECK (budget > 0),
CONSTRAINT chk_date_range CHECK (end_date >= start_date),
CONSTRAINT chk_budget_limit CHECK (budget <= 1000000)
);
Output:
CREATE TABLE
(2) أنماط CHECK الشائعة
| قاعدة العمل | تعبير CHECK |
|---|---|
| المبلغ موجب | CHECK (amount > 0) |
| نطاق الخصم | CHECK (discount BETWEEN 0 AND 1) |
| ترتيب التواريخ | CHECK (end_date >= start_date) |
| قيم تعداد | CHECK (status IN ('active', 'inactive', 'pending')) |
| طول السلسلة | CHECK (LENGTH(phone) >= 10) |
| نسبة مئوية | CHECK (rate >= 0 AND rate <= 100) |
(12) ▶ مثال
CREATE TABLE subscriptions (
sub_id BIGSERIAL PRIMARY KEY,
plan VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL,
CONSTRAINT chk_price_positive CHECK (price > 0),
CONSTRAINT chk_plan_valid CHECK (plan IN ('free', 'basic', 'pro', 'enterprise'))
);
Output:
CREATE TABLE
(13) ▶ مثال
ALTER TABLE orders
ADD CONSTRAINT chk_amount_positive CHECK (total_amount > 0);
Output:
-- تم تنفيذ عبارة SQL بنجاح
7. المفهوم: قيد EXCLUSION
(1) كيفية عمل EXCLUSION
يضمن قيد EXCLUSION: إذا كان صفان "متساويين" في أعمدة محددة (مقارنة بعامل =)، فإنهما لا "ي��داخلان" في البعد المحدد (مقارنة بعامل تداخل). هذه ميزة خاصة بـ PostgreSQL.
| السيناريو | القيد | العامل |
|---|---|---|
| نطاقات زمنية غير متداخلة | أوقات نفس المورد لا يمكن أن تتقاطع | =, && |
| عدم تكرار المقاعد | أرقام مقاعد نفس الجلسة لا تتكرر | =, = (مكافئ لـ UNIQUE) |
(14) ▶ مثال
CREATE TABLE room_bookings (
booking_id BIGSERIAL PRIMARY KEY,
room_id INT NOT NULL,
time_range TSTZRANGE NOT NULL,
booked_by VARCHAR(100) NOT NULL,
CONSTRAINT excl_room_no_overlap
EXCLUDE USING GiST (room_id WITH =, time_range WITH &&)
);
Output:
CREATE TABLE
(15) ▶ مثال
INSERT INTO room_bookings (room_id, time_range, booked_by)
VALUES (1, '[2025-07-13 09:00, 2025-07-13 11:00)', 'Alice');
INSERT INTO room_bookings (room_id, time_range, booked_by)
VALUES (1, '[2025-07-13 10:00, 2025-07-13 12:00)', 'Bob');
ERROR: conflicting key value violates exclusion constraint "excl_room_no_overlap"
DETAIL: Key (room_id, time_range)=(1, [2025-07-13 10:00,2025-07-13 12:00))
conflicts with existing key (room_id, time_range)=(1, [2025-07-13 09:00,2025-07-13 11:00)).
(2) مقارنة EXCLUSION مقابل UNIQUE
| البعد | UNIQUE | EXCLUSION |
|---|---|---|
| عامل المقارنة | = فقط |
مخصص (=، &&، <->، إلخ) |
| تداخل النطاق | غير مدعوم | مدعوم |
| نوع الفهرس | B-Tree | GiST / SP-GiST |
| المرونة | منخفضة | عالية |
| حالة الاستخدام | تفرد القيم المنفصلة | نطاقات غير متداخلة، قيود المسافة |
(16) ▶ مثال
CREATE TABLE discount_tiers (
tier_id BIGSERIAL PRIMARY KEY,
product_id INT NOT NULL,
price_range NUMRANGE NOT NULL,
discount DECIMAL(5,4) NOT NULL,
CONSTRAINT excl_price_no_overlap
EXCLUDE USING GiST (product_id WITH =, price_range WITH &&)
);
Output:
CREATE TABLE
8. المفهوم: قيد DEFERRABLE المؤجل
(1) آلية القيد المؤجل
افتراضيًا، يتم التحقق من القيود فورًا بعد كل عبارة. يسمح قيد DEFERRABLE بتأجيل التحقق حتى إيداع المعاملة، مما يحل مشكلة المرجع الدائري في العمليات الجماعية.
| الوضع | توقيت التحقق | الصيغة |
|---|---|---|
| IMMEDIATE | بعد كل عبارة | السلوك الافتراضي |
| DEFERRABLE INITIALLY IMMEDIATE | بعد كل عبارة (قابل للتبديل) | DEFERRABLE INITIALLY IMMEDIATE |
| DEFERRABLE INITIALLY DEFERRED | عند إيداع المعاملة | DEFERRABLE INITIALLY DEFERRED |
(17) ▶ مثال
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
manager_id INT
);
ALTER TABLE departments
ADD CONSTRAINT fk_dept_manager
FOREIGN KEY (manager_id) REFERENCES departments(dept_id)
DEFERRABLE INITIALLY DEFERRED;
Output:
CREATE TABLE
(18) ▶ مثال
BEGIN;
INSERT INTO departments (dept_id, name, manager_id)
VALUES (1, 'Engineering', 2);
INSERT INTO departments (dept_id, name, manager_id)
VALUES (2, 'QA', 1);
COMMIT;
Output:
INSERT 0 1
تنفيذ INSERT الأول بمفرده سيفشل (manager_id=2 غير موجود بعد)، لكن DEFERRABLE يؤجل التحقق حتى COMMIT، لذا بمجرد اكتمال كلا INSERT ينجح التحقق.
(2) تبديل توقيت التحقق ديناميكيًا
(19) ▶ مثال
BEGIN;
SET CONSTRAINTS fk_dept_manager DEFERRED;
INSERT INTO departments (dept_id, name, manager_id) VALUES (3, 'Sales', 4);
INSERT INTO departments (dept_id, name, manager_id) VALUES (4, 'Marketing', 3);
SET CONSTRAINTS fk_dept_manager IMMEDIATE;
COMMIT;
Output:
INSERT 0 1
(3) مقارنة IMMEDIATE مقابل DEFERRED
| البعد | IMMEDIATE | DEFERRED |
|---|---|---|
| توقيت التحقق | بعد كل عبارة | عند إيداع المعاملة |
| الأداء | أسرع (تغذية راجعة فورية) | أبطأ قليلاً (تحقق دفعة) |
| المرجع الدائري | لا يمكن حله | قابل للحل |
| المخاط�� | منخفضة | اكتشاف الانتهاك فقط في نهاية المعاملة |
| حالة الاستخدام | معظم القيود | المراجع الدائرية، الاستيراد الجماعي |
9. المفهوم: تسمية القيود وإدارتها
(1) اصطلاح تسمية القيود
التسمية الجيدة تسهل تحديد المشاكل وتنفيذ العمليات.
| نوع القيد | البادئة الموصى بها | مثال |
|---|---|---|
| PRIMARY KEY | pk_ | pk_orders |
| FOREIGN KEY | fk_ | fk_orders_customer |
| UNIQUE | uq_ | uq_users_email |
| CHECK | chk_ | chk_amount_positive |
| EXCLUSION | excl_ | excl_room_no_overlap |
(20) ▶ مثال
CREATE TABLE orders (
order_id BIGSERIAL CONSTRAINT pk_orders PRIMARY KEY,
customer_id BIGINT NOT NULL CONSTRAINT fk_orders_customer
REFERENCES customers(customer_id) ON DELETE CASCADE,
total_amount DECIMAL(12,2) CONSTRAINT chk_amount_positive CHECK (total_amount > 0),
status VARCHAR(20) CONSTRAINT chk_status_valid
CHECK (status IN ('pending', 'shipped', 'delivered', 'cancelled')),
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Output:
CREATE TABLE
(2) عمليات إدارة القيود
| العملية | الصيغة |
|---|---|
| إضافة قيد | ALTER TABLE t ADD CONSTRAINT name CHECK (...) |
| حذف قيد | ALTER TABLE t DROP CONSTRAINT name |
| عرض القيود | SELECT * FROM pg_constraint WHERE conrelid = 't'::regclass |
| تعطيل المشغلات (تعطيل القيود بشكل غير مباشر) | ALTER TABLE t DISABLE TRIGGER ALL |
(21) ▶ مثال
SELECT
conname AS constraint_name,
contype AS type,
pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'orders'::regclass;
constraint_name | type | definition
-----------------------+------+--------------------------------------------
pk_orders | p | PRIMARY KEY (order_id)
fk_orders_customer | f | FOREIGN KEY (customer_id) REFERENCES ...
chk_amount_positive | c | CHECK ((total_amount > 0))
chk_status_valid | c | CHECK ((status = ANY (ARRAY['pending'::...
10. مثال شامل
مخطط قيود منصة SaaS لـ Charlie — يغطي المفتاح الأساسي، تعاقب المفتاح الخارجي، CHECK، EXCLUSION، DEFERRABLE:
CREATE TABLE customers (
customer_id BIGSERIAL CONSTRAINT pk_customers PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) CONSTRAINT uq_customers_email UNIQUE,
credit_limit DECIMAL(12,2) DEFAULT 0
CONSTRAINT chk_credit_non_negative CHECK (credit_limit >= 0)
);
CREATE TABLE orders (
order_id BIGSERIAL CONSTRAINT pk_orders PRIMARY KEY,
customer_id BIGINT NOT NULL CONSTRAINT fk_orders_customer
REFERENCES customers(customer_id) ON DELETE RESTRICT,
total_amount DECIMAL(12,2) NOT NULL
CONSTRAINT chk_amount_positive CHECK (total_amount > 0),
status VARCHAR(20) DEFAULT 'pending'
CONSTRAINT chk_status CHECK (status IN ('pending','shipped','delivered','cancelled')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE room_bookings (
booking_id BIGSERIAL CONSTRAINT pk_bookings PRIMARY KEY,
room_id INT NOT NULL,
time_range TSTZRANGE NOT NULL,
booked_by VARCHAR(100) NOT NULL,
CONSTRAINT excl_room_no_overlap
EXCLUDE USING GiST (room_id WITH =, time_range WITH &&)
);
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer_deferred
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT
DEFERRABLE INITIALLY IMMEDIATE;
11. تدفق التنفيذ
أين يحدث التحقق من القيود في تنفيذ عبارة DML:
flowchart TD
A[INSERT/UPDATE/DELETE] --> B{فحص NOT NULL}
B -->|نجاح| C{فحص CHECK}
B -->|فشل| Z[خطأ، تراجع]
C -->|نجاح| D{فحص UNIQUE}
C -->|فشل| Z
D -->|نجاح| E{فحص PRIMARY KEY}
D -->|فشل| Z
E -->|نجاح| F{فحص EXCLUSION}
E -->|فشل| Z
F -->|نجاح| G{توقيت القيد؟}
F -->|فشل| Z
G -->|IMMEDIATE| H[فحص FK فورًا]
G -->|DEFERRED| I[تأجيل فحص FK حتى COMMIT]
H -->|نجاح| J[اكتمال العبارة]
I --> K[COMMIT]
K --> L[فحص جميع FK المؤجلة]
L -->|نجاح| M[إيداع المعاملة]
L -->|فشل| Z
style Z fill:#ffccbc
style J fill:#c8e6c9
style M fill:#c8e6c9
❓ أسئلة شائعة
📖 ملخص
- PRIMARY KEY = UNIQUE + NOT NULL، واحد لكل جدول، ينشئ فهرسًا تلقائيًا
- FOREIGN KEY له خمس استراتيجيات تعاقب: CASCADE / SET NULL / SET DEFAULT / RESTRICT / NO ACTION
- CASCADE مريح لكنه م��فوف بالمخاطر؛ RESTRICT آمن لكنه يحتاج تنظيفًا يدويًا
- UNIQUE يسمح بعدة NULL؛ PRIMARY KEY لا يسمح بـ NULL على الإطلاق
- قيد CHECK يمكنه الإشارة إلى أعمدة أخرى في نفس الصف، مثالي للتحقق المنطقي متعدد الأعمدة
- قيد EXCLUSION (ميزة PG) يضمن نطاقات غير متداخلة، مثلاً: حجوزات غرف الاجتماعات
- قيد DEFERRABLE المؤجل (ميزة PG) يحل مشكلات المراجع الدائرية والاستيراد الجماعي
- تسمية القيود بشكل صريح تساعد في الإدارة والعمليات؛ يوصى بالبادئات pk_/fk_/uq_/chk_/excl_
📝 تمارين
-
⭐ أنشئ جدولي
customersوorders، عرف المفاتيح الأساسية والخارجية (ON DELETE CASCADE)، وأضف قيد CHECK يضمن أن مبلغ الطلب > 0. -
⭐ أنشئ جدول
productsمع عمود SKU فريد وقيد CHECK يضمن السعر > 0 والخصم <= السعر. -
⭐⭐ استخدم قيد EXCLUSION لإنشاء جدول
room_bookings، مع ضمان عدم تداخل النطاقات الزمنية لنفس غرفة الاجتماعات، واختبر إدراجًا متعارضًا. -
⭐⭐ أنشئ جدولين بمرجع متبادل (employees يشير إلى departments، و manager_id في departments يشير إلى employees)، واستخدم DEFERRABLE لحل المرجع الدائري.
-
⭐⭐⭐ صمم نموذج بيانات SaaS كامل للمستأجرين مع: جدول tenants، جدول users (تعاقب مفتاح خارجي)، جدول subscriptions (CHECK يتحقق من التواريخ والمبالغ)، جدول resource_booking (EXCLUSION يمنع تداخل الوقت)، وقم بتسمية جميع القيود.