القيود والشروط (Constraints)
القيود (Constraints) في SQL هي قواعد وشروط نطبقها على الأعمدة والجداول لضمان دقة وسلامة البيانات (Data Integrity). تعتبر القيود من أهم الأدوات التي توفرها قواعد البيانات العلائقية لمنع إدخال بيانات غير صحيحة أو متناقضة، وللحفاظ على العلاقات المنطقية بين الجداول. في هذا الدرس الشامل والمفصل من سلسلة تعلم لغة SQL باللغة العربية، سنتعلم جميع أنواع القيود في SQL: PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK, DEFAULT، وكيفية استخدام كل منها بشكل احترافي مع أمثلة عملية تطبيقية تغطي سيناريوهات واقعية من عالم تطوير التطبيقات وقواعد البيانات.
1. ما هي القيود (Constraints) ولماذا نحتاجها؟
القيود (Constraints) هي قواعد نفرضها على البيانات في قاعدة البيانات لضمان صحتها ودقتها. تخيل أنك تدير نظام تسجيل طلاب في جامعة. بدون قيود، قد يحدث التالي: طالب بدون رقم معرف، طالبان بنفس رقم الهوية، طالب بعمر سالب (-5 سنوات)، أو طالب مسجل في قسم غير موجود. كل هذه المشاكل يمكن منعها باستخدام القيود المناسبة.
القيود تعمل على مستويين: مستوى العمود (Column Level) حيث تطبق القيد على عمود واحد فقط، ومستوى الجدول (Table Level) حيث يمكن أن يشمل القيد عدة أعمدة. عندما تحاول إدخال أو تحديث بيانات تنتهك أحد القيود، سترفض قاعدة البيانات العملية وتعرض رسالة خطأ، مما يحمي بياناتك من الفساد.
من الناحية المهنية، استخدام القيود بشكل صحيح هو علامة على التصميم الاحترافي لقاعدة البيانات. في الشركات الكبرى والمؤسسات المالية، سلامة البيانات ليست مجرد ميزة إضافية، بل هي متطلب أساسي وحرج. خطأ واحد في البيانات المالية أو الطبية قد يكلف الشركة ملايين الدولارات أو يعرض حياة المرضى للخطر. لذلك، فهم القيود واستخدامها بشكل صحيح هو مهارة أساسية لأي مطور أو مدير قواعد بيانات محترف.
2. PRIMARY KEY - المفتاح الأساسي
المفتاح الأساسي (Primary Key) هو أهم قيد في قواعد البيانات العلائقية. يُستخدم لتحديد كل صف في الجدول بشكل فريد ومميز. كل جدول يجب أن يحتوي على مفتاح أساسي واحد فقط (لكن يمكن أن يتكون من عمود واحد أو عدة أعمدة).
خصائص المفتاح الأساسي
- الفرادة (Uniqueness): لا يمكن أن يكون هناك صفان بنفس قيمة المفتاح الأساسي
- عدم القبول بـ NULL: المفتاح الأساسي لا يمكن أن يكون فارغاً أبداً
- واحد لكل جدول: كل جدول يمكن أن يحتوي على مفتاح أساسي واحد فقط
- الفهرسة التلقائية: يتم إنشاء فهرس (Index) تلقائياً لتسريع البحث
- الثبات: يُفضل ألا تتغير قيمة المفتاح الأساسي بعد الإنشاء
طرق تعريف المفتاح الأساسي
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
hire_date DATE NOT NULL
);
CREATE TABLE employees (
employee_id INT AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
hire_date DATE NOT NULL,
PRIMARY KEY (employee_id)
);
المفتاح الأساسي المركب (Composite Primary Key)
في بعض الحالات، نحتاج إلى استخدام عمودين أو أكثر معاً كمفتاح أساسي. هذا يسمى المفتاح الأساسي المركب:
-- جدول تسجيل الطلاب في المقررات
CREATE TABLE course_enrollments (
student_id INT NOT NULL,
course_id INT NOT NULL,
enrollment_date DATE NOT NULL,
grade DECIMAL(5, 2),
PRIMARY KEY (student_id, course_id)
);
في هذا المثال، الطالب الواحد يمكن أن يسجل في عدة مقررات، والمقرر الواحد يمكن أن يحتوي على عدة طلاب. لكن الطالب لا يمكن أن يسجل في نفس المقرر مرتين. لذلك، المفتاح الأساسي المركب من student_id و course_id يضمن هذا.
3. FOREIGN KEY - المفتاح الخارجي
المفتاح الخارجي (Foreign Key) هو عمود أو مجموعة أعمدة في جدول تشير إلى المفتاح الأساسي في جدول آخر. يُستخدم لإنشاء علاقات بين الجداول والحفاظ على التكامل المرجعي (Referential Integrity). هذا يعني أنك لا تستطيع إضافة قيمة في المفتاح الخارجي إلا إذا كانت موجودة في المفتاح الأساسي للجدول المرجعي.
مثال عملي: علاقة بين الموظفين والأقسام
CREATE TABLE departments (
department_id INT PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(100) NOT NULL,
location VARCHAR(100)
);
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
department_id INT,
hire_date DATE NOT NULL,
-- تعريف المفتاح الخارجي
FOREIGN KEY (department_id) REFERENCES departments(department_id)
);
الآن، لا يمكنك إضافة موظف بـ department_id = 5 إلا إذا كان القسم رقم 5 موجوداً فعلاً في جدول departments. هذا يمنع البيانات اليتيمة (Orphaned Data).
خيارات المفتاح الخارجي: ON DELETE و ON UPDATE
عند استخدام المفاتيح الخارجية، يمكنك تحديد ماذا يحدث عندما يتم حذف أو تحديث الصف المرجعي في الجدول الرئيسي:
| الخيار | الوصف |
|---|---|
CASCADE |
حذف/تحديث الصفوف المرتبطة تلقائياً |
SET NULL |
تعيين المفتاح الخارجي إلى NULL |
RESTRICT |
منع الحذف/التحديث إذا كانت هناك صفوف مرتبطة |
NO ACTION |
مثل RESTRICT (الافتراضي) |
SET DEFAULT |
تعيين قيمة افتراضية |
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
department_id INT,
FOREIGN KEY (department_id)
REFERENCES departments(department_id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
مع ON DELETE CASCADE، إذا حذفت قسماً من جدول departments، سيتم حذف جميع الموظفين في ذلك القسم تلقائياً. استخدم هذا الخيار بحذر!
4. UNIQUE - ضمان القيم الفريدة
القيد UNIQUE يضمن أن جميع القيم في العمود (أو مجموعة أعمدة) فريدة ولا تتكرر. على عكس المفتاح الأساسي، يمكن أن يكون لديك عدة أعمدة بقيد UNIQUE في نفس الجدول، ويمكن أن تحتوي على قيم NULL (لكن قيمة NULL واحدة فقط في معظم الأنظمة).
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255) NOT NULL
);
في هذا المثال، لا يمكن أن يكون هناك مستخدمان بنفس اسم المستخدم أو نفس البريد الإلكتروني أو نفس رقم الهاتف.
UNIQUE على عدة أعمدة (Composite Unique)
CREATE TABLE product_reviews (
review_id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT NOT NULL,
user_id INT NOT NULL,
rating INT NOT NULL,
review_text TEXT,
review_date DATETIME DEFAULT CURRENT_TIMESTAMP,
-- المستخدم يمكنه كتابة مراجعة واحدة فقط لكل منتج
UNIQUE (product_id, user_id)
);
تسمية القيود (Named Constraints)
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
CONSTRAINT unique_username UNIQUE (username),
CONSTRAINT unique_email UNIQUE (email)
);
إعطاء أسماء للقيود يسهل إدارتها وحذفها لاحقاً، ويجعل رسائل الخطأ أكثر وضوحاً.
5. NOT NULL - منع القيم الفارغة
القيد NOT NULL يضمن أن العمود يجب أن يحتوي على قيمة، ولا يمكن أن يكون فارغاً (NULL). هذا القيد بسيط لكنه مهم جداً لضمان اكتمال البيانات.
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
-- معلومات إلزامية
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
-- معلومات اختيارية
phone VARCHAR(20),
address TEXT,
company_name VARCHAR(100),
-- تاريخ التسجيل إلزامي
registration_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
في هذا المثال، لا يمكن إنشاء عميل بدون اسم أول، اسم أخير، أو بريد إلكتروني. لكن رقم الهاتف والعنوان واسم الشركة اختيارية.
نصيحة مهمة:
استخدم NOT NULL لجميع الأعمدة التي تعتبر ضرورية لمنطق التطبيق. هذا يمنع البيانات الناقصة ويسهل كتابة الاستعلامات لاحقاً.
6. DEFAULT - القيمة الافتراضية
القيد DEFAULT يحدد قيمة افتراضية للعمود إذا لم يتم تحديد قيمة عند إدراج صف جديد. هذا مفيد جداً لتبسيط عمليات الإدراج وضمان وجود قيم معقولة.
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
-- تاريخ الطلب يتم تعيينه تلقائياً للوقت الحالي
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
-- حالة الطلب الافتراضية "pending"
status VARCHAR(20) DEFAULT 'pending',
-- المبلغ الافتراضي صفر
total_amount DECIMAL(10, 2) DEFAULT 0.00,
-- الشحن مجاني افتراضياً
shipping_cost DECIMAL(10, 2) DEFAULT 0.00,
-- الطلب نشط افتراضياً
is_active BOOLEAN DEFAULT TRUE,
-- الملاحظات اختيارية
notes TEXT
);
DEFAULT مع التحديث التلقائي
CREATE TABLE posts (
post_id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
author_id INT NOT NULL,
-- يتم تعيينه عند الإنشاء
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- يتم تحديثه تلقائياً عند أي تعديل
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
7. CHECK - قيود مخصصة
القيد CHECK يسمح لك بتحديد شرط منطقي يجب أن تحققه البيانات. هذا يعطيك مرونة كبيرة في تطبيق قواعد عمل معقدة على مستوى قاعدة البيانات.
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100) NOT NULL,
-- السعر يجب أن يكون موجباً
price DECIMAL(10, 2) NOT NULL CHECK (price > 0),
-- الكمية لا يمكن أن تكون سالبة
quantity INT NOT NULL CHECK (quantity >= 0),
-- نسبة الخصم بين 0 و 100
discount_percentage DECIMAL(5, 2) DEFAULT 0 CHECK (discount_percentage BETWEEN 0 AND 100),
-- التقييم من 1 إلى 5
rating DECIMAL(2, 1) CHECK (rating >= 1 AND rating <= 5)
);
CHECK على عدة أعمدة
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
birth_date DATE NOT NULL,
hire_date DATE NOT NULL,
salary DECIMAL(10, 2) NOT NULL,
-- تاريخ التوظيف يجب أن يكون بعد تاريخ الميلاد بـ 18 سنة على الأقل
CHECK (YEAR(hire_date) - YEAR(birth_date) >= 18),
-- الراتب يجب أن يكون بين 3000 و 100000
CHECK (salary BETWEEN 3000 AND 100000)
);
CHECK مع قيم محددة
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
-- الحالة يجب أن تكون واحدة من القيم المحددة
status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled')),
-- طريقة الدفع
payment_method VARCHAR(20)
CHECK (payment_method IN ('cash', 'credit_card', 'debit_card', 'paypal', 'bank_transfer'))
);
ملاحظة مهمة:
في MySQL الإصدارات القديمة (قبل 8.0.16)، كان قيد CHECK يُقبل في الصيغة لكن لا يتم تطبيقه فعلياً. من MySQL 8.0.16 فما فوق، يتم تطبيق CHECK بشكل كامل.
8. مثال عملي شامل: نظام إدارة مكتبة متكامل
لنطبق جميع أنواع القيود في مثال واقعي متكامل لنظام إدارة مكتبة:
-- 1. جدول الأعضاء
CREATE TABLE members (
member_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) UNIQUE,
birth_date DATE NOT NULL,
membership_date DATE DEFAULT (CURRENT_DATE),
membership_type VARCHAR(20) DEFAULT 'standard'
CHECK (membership_type IN ('standard', 'premium', 'vip')),
is_active BOOLEAN DEFAULT TRUE,
-- العضو يجب أن يكون عمره 13 سنة على الأقل
CHECK (YEAR(CURRENT_DATE) - YEAR(birth_date) >= 13)
);
-- 2. جدول الكتب
CREATE TABLE books (
book_id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
author VARCHAR(100) NOT NULL,
isbn VARCHAR(13) UNIQUE NOT NULL,
publisher VARCHAR(100),
publication_year INT CHECK (publication_year BETWEEN 1000 AND YEAR(CURRENT_DATE)),
category VARCHAR(50) DEFAULT 'General',
price DECIMAL(10, 2) NOT NULL CHECK (price > 0),
quantity_total INT NOT NULL DEFAULT 1 CHECK (quantity_total >= 0),
quantity_available INT NOT NULL DEFAULT 1 CHECK (quantity_available >= 0),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- الكمية المتاحة لا يمكن أن تكون أكبر من الكمية الإجمالية
CHECK (quantity_available <= quantity_total)
);
-- 3. جدول الاستعارات
CREATE TABLE borrowings (
borrowing_id INT PRIMARY KEY AUTO_INCREMENT,
member_id INT NOT NULL,
book_id INT NOT NULL,
borrow_date DATE DEFAULT (CURRENT_DATE),
due_date DATE NOT NULL,
return_date DATE,
fine_amount DECIMAL(10, 2) DEFAULT 0.00 CHECK (fine_amount >= 0),
status VARCHAR(20) DEFAULT 'borrowed'
CHECK (status IN ('borrowed', 'returned', 'overdue', 'lost')),
-- المفاتيح الخارجية
FOREIGN KEY (member_id) REFERENCES members(member_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
FOREIGN KEY (book_id) REFERENCES books(book_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
-- تاريخ الإرجاع المتوقع يجب أن يكون بعد تاريخ الاستعارة
CHECK (due_date > borrow_date),
-- تاريخ الإرجاع الفعلي (إذا وُجد) يجب أن يكون بعد تاريخ الاستعارة
CHECK (return_date IS NULL OR return_date >= borrow_date)
);
9. عرض القيود الموجودة في الجدول
لعرض جميع القيود المطبقة على جدول معين، يمكنك استخدام الاستعلامات التالية:
-- عرض بنية الجدول
DESCRIBE members;
-- عرض أمر الإنشاء الكامل مع جميع القيود
SHOW CREATE TABLE members;
-- عرض القيود من جداول النظام
SELECT
CONSTRAINT_NAME,
CONSTRAINT_TYPE,
TABLE_NAME
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = 'your_database_name'
AND TABLE_NAME = 'members';
10. أفضل الممارسات في استخدام القيود
افعل (DO)
- استخدم المفاتيح الأساسية في كل جدول
- استخدم المفاتيح الخارجية للحفاظ على التكامل المرجعي
- استخدم NOT NULL للأعمدة الإلزامية
- استخدم UNIQUE لمنع التكرار
- استخدم CHECK للتحقق من صحة البيانات
- أعطِ أسماء واضحة للقيود
- وثق القيود في تصميم قاعدة البيانات
لا تفعل (DON'T)
- لا تترك جداول بدون مفاتيح أساسية
- لا تستخدم CASCADE بدون تفكير عميق
- لا تتجاهل القيود لتسهيل التطوير
- لا تعتمد فقط على التحقق في التطبيق
- لا تستخدم قيود معقدة جداً تؤثر على الأداء
- لا تنسَ توثيق القيود المخصصة
ملخص الدرس
في هذا الدرس الشامل، تعلمنا جميع أنواع القيود في SQL وكيفية استخدامها لضمان سلامة البيانات:
- PRIMARY KEY: لتحديد الصفوف بشكل فريد
- FOREIGN KEY: لإنشاء علاقات بين الجداول
- UNIQUE: لمنع التكرار
- NOT NULL: لضمان وجود قيم
- DEFAULT: لتحديد قيم افتراضية
- CHECK: لتطبيق قواعد مخصصة
القيود هي خط الدفاع الأول ضد البيانات الفاسدة أو غير الصحيحة. استخدامها بشكل صحيح يوفر عليك الكثير من المشاكل في المستقبل.
الخطوة التالية: التطبيع (Database Normalization)
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم التطبيع (Database Normalization) وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي