انتقال للمقال

ما هى أنواع الـ SQL Constraints المختلفة

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 20 دقيقة (بمعدل فنجان واحد من الشاي 😊)

المقدمة

في المقالة السابقة تعلمنا كيفية إنشاء قواعد البيانات والجداول باستخدام أمر CREATE
ولاحظنا أثناء إنشاء الجداول أننا استخدمنا بعض الكلمات مثل NOT NULL و PRIMARY KEY و DEFAULT

هذه الكلمات تسمى Constraints في الـ SQL
وهي قواعد وشروط نضعها على الجداول والأعمدة لضمان سلامة البيانات

في هذه المقالة سنتعلم كل شيء عن هذه القيود وكيفية استخدامها

ما هي قيود الـ SQL ؟

الـ SQL Constraints هي قواعد وشروط نطبقها على البيانات في الجداول
الغرض منها هو التأكد من صحة البيانات وسلامتها وهى كما يوحي اسمها قيود على البيانات

تخيل معي أنك تقوم بإنشاء تطبيق لإدارة حسابات المستخدمين
هنا ستحتاج إلى التأكد من أن:

  • كل مستخدم له بريد إلكتروني لا يتكرر
  • لا يمكن إنشاء حساب بدون اسم مستخدم
  • عمر المستخدم يجب أن يكون أكبر من 13 سنة مثلًا
  • إذا لم يحدد المستخدم الدولة، فستكون القيمة الافتراضية "غير محدد"

كل هذه الأمور يمكننا تطبيقها على الجدول باستخدام الـ SQL Constraints

أنواع قيود الـ SQL

هناك عدة أنواع من القيود في الـ SQL، وسنتعلم كل نوع بالتفصيل:

  • NOT NULL: يمنع أن يكون العمود فارغًا
  • UNIQUE: يضمن أن كل القيم في العمود مختلفة ولا تتكرر
  • PRIMARY KEY: هو مثل رقم الهوية الخاص بكل صف في الجدول، ويجمع بين NOT NULL و UNIQUE
  • FOREIGN KEY: يربط بين جدولين
  • CHECK: يتحقق من شرط معين
  • DEFAULT: يضع قيمة افتراضية في العمود إذا لم يتم تحديد قيمة

دعونا نتعلم كل واحد منها بالتفصيل

الـ NOT NULL

في حالة أنك أنشأت عمودًا في جدول ولم تحدد له قيمة، فإن قاعدة البيانات ستضع له قيمة NULL
وهذا يجعل العمود يقبل أن يكون فارغًا وبالطبع هذا الأمر ليس جيدًا في بعض الحالات
لأنك دائمًا ما ستحتاج إلى التأكد من أن بعض الأعمدة تحتوي على قيم ولا تريدها أن تكون فارغة

لذلك في هذه الحالة نستخدم Constraint الـ NOT NULL يعني أن العمود لا يمكن أن يكون فارغًا أبدًا
وإذا حاول أحد إدخال صف بدون قيمة في هذا العمود، ستظهر له رسالة خطأ

لنرى مثالًا على ذلك

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL,
    age INT,
    level VARCHAR(50)
);

في هذا المثال، جعلنا الأعمدة id و name و email لا تقبل NULL
بينما age و level يمكن أن يكونا فارغين

ويمكنك التأكد من ذلك باستخدام الأمر DESCRIBE Students;

+-------+------------------+-------+-----+---------+----------------+
| Field | Type             | Null  | Key | Default | Extra          |
+-------+------------------+-------+-----+---------+----------------+
| id    | int              | NO    |     | NULL    |                |
| name  | varchar(100)     | NO    |     | NULL    |                |
| email | varchar(100)     | NO    |     | NULL    |                |
| age   | int              | YES   |     | NULL    |                |
| level | varchar(50)      | YES   |     | NULL    |                |
+-------+------------------+-------+-----+---------+----------------+

كما ترى العمود name و email و id لا يقبلون NULL
بينما العمود age و level يقبلان NULL

بالتالي في حالة محاولة إدخال طالب جديد بدون اسم باستخدام الأمر INSERT:

INSERT INTO Students (id, email, age)
VALUES (1, '[email protected]', 20);

لاحظ أننا هنا نضيف طالب جديد بدون name
وتذكر أننا جعلنا name لا يقبل NULL ولا يملك قيمة افتراضية

بالتالي إن حاولنا تنفيذ هذا الأمر، ستظهر رسالة خطأ مثل:

SQL Error: Field 'name' doesn't have a default value

أعلم أننا لم نشرح الأمر INSERT بعد، لكن لا تقلق، سنغطيه في المقالة القادمة
لكن كل ما عليك أن تعرفه الآن هو أن هذا الأمر INSERT يستخدم لإضافة بيانات جديدة إلى الجدول
وهنا نحن حاولنا إضافة صف واحد في جدول Students بالبيانات التالية بالترتيب بحيث أن id هو 1 و email هو '[email protected]' و age هو 20

لكن الفكرة هنا هي أن الـ Constraint المسمى NOT NULL الذي وضعناه على العمود name
يمنعنا من إدخال صف بدون قيمة في هذا العمود
بالتالي لأننا لم نعطي قيمة للعمود name، فتظهر لنا رسالة تخبرنا أن العمود name لا يملك قيمة افتراضية


إذا كان لديك جدول موجود بالفعل وتريد إضافة NOT NULL لعمود معين
فنحن نستطيع فعل ذلك باستخدام الأمر ALTER TABLE مع MODIFY أو ALTER COLUMN حسب نوع الـ DBMS التي تستخدمها

ALTER TABLE Students
MODIFY name VARCHAR(100) NOT NULL;

ملحوظة: لا يمكن إضافة Constraint الـ NOT NULL في عمود معين في حالة وجود بيانات في الجدول تحتوي على قيم NULL في هذا العمود
في هذه الحالة، يجب عليك أولًا تحديث هذه الصفوف للتأكد من عدم وجود أي صفوف تحتوي على NULL في هذا العمود قبل إضافة الـ Constraint

الـ UNIQUE

أحيانًا تحتاج إلى التأكد من أن القيم في عمود معين لا تتكرر أبدًا
مثلًا، لا يمكن أن يكون هناك طالبان لهما نفس البريد الإلكتروني أو نفس رقم الطالب

هنا يأتي دور قيد UNIQUE الذي يضمن أن كل القيم في العمود مختلفة
فإذا حاول أحد إدخال قيمة موجودة بالفعل، ستظهر رسالة خطأ

لنتخيل أننا نريد إنشاء جدول للطلاب، ونريد أن يكون لكل طالب بريد إلكتروني مختلف أي لا يمكن تكراره

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    age INT,
    level VARCHAR(50)
);

في هذا المثال، جعلنا email يكون UNIQUE
بمعنى أنه لا يمكن أن يكون هناك طالبان لهما نفس البريد الإلكتروني

لنرى ماذا سيقول لنا أمر الـ DESCRIBE Students; بعد إنشاء الجدول:

+-------+------------------+-------+-----+---------+----------------+
| Field | Type             | Null  | Key | Default | Extra          |
+-------+------------------+-------+-----+---------+----------------+
| id    | int              | NO    |     | NULL    |                |
| name  | varchar(100)     | NO    |     | NULL    |                |
| email | varchar(100)     | YES   | UNI | NULL    |                |
| age   | int              | YES   |     | NULL    |                |
| level | varchar(50)      | YES   |     | NULL    |                |
+-------+------------------+-------+-----+---------+----------------+

هنا العمود email يحمل علامة UNI، مما يعني أنه UNIQUE

ماذا يحدث عند محاولة التكرار ؟

لنفترض أننا أدخلنا طالبين، والثاني له نفس البريد الإلكتروني:

INSERT INTO Students (id, name, email, age, level)
VALUES (1, 'Ahmed Moustafa', '[email protected]', 20, 'Beginner');

INSERT INTO Students (id, name, email, age, level)
VALUES (2, 'Ahmed Ali', '[email protected]', 22, 'Intermediate');

في هذه الحالة الطالب الأول سينجح في التسجيل بنجاح لأنه لا يوجد طالب آخر بنفس البريد الإلكتروني
ثم عندما نحاول تسجيل الطالب الثاني بنفس البريد الإلكتروني [email protected]، ستظهر رسالة خطأ

SQL ERROR: Duplicate entry '[email protected]' for key 'Students.email'

وهذا بالضبط ما نريده، لأننا لا نريد طالبين لهما نفس البريد الإلكتروني


يمكنك كتابة الـ Constraint الخاص بالـ UNIQUE عند إنشاء الجدول باستخدام CREATE TABLE بطريقة أخرى:

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    age INT,
    level VARCHAR(50),

    CONSTRAINT unique_email UNIQUE (email)
);

هنا استخدمنا CONSTRAINT unique_email UNIQUE (email) لتحديد العمود email كـ UNIQUE
وهذه طريقة تدعمها معظم الـ DBMS
وأيضًا يوجد طريقة أبسط تستخدمها في بعض الـ DBMS مثل MySQL:

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    age INT,
    level VARCHAR(50),

    UNIQUE (email)
);

لاحظ أننا لم نستخدم CONSTRAINT هنا، بل فقط كتبنا UNIQUE (email) بشكل مباشر
وهذه الطريقة تعمل في معظم أنظمة إدارة قواعد البيانات مثل MySQL


سؤال، الآن نحن قمنا بجعل الـ email يكون UNIQUE
فهل هذا يعني أنه لا يقبل NULL ؟
لوهلة الأولى قد تقول لي بالطبع لا يقبل NULL
لكن إن فكرت في الأمر قليلًا، ستجد العمود الذي عليه UNIQUE يمكن أن يحتوي على قيم NULL
لكن يمكن أن يكون هناك صف واحد فقط يحتوي على NULL في هذا العمود

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,  -- NULL يمكن أن يقبل
    age INT,
    level VARCHAR(50)
);

هنا العمود email يمكن أن يحتوي على قيمة NULL
بمعنى أنه يمكن أن يكون لدينا طالب واحد تكون قيمة الـ email عنده بـ NULL
كأن قيمة الـ NULL هي قيمة بحد ذاتها بالتالي فأن الـ UNIQUE سيتعامل معها كأنها قيمة مستقلة مثل أي قيمة أخرى

وإذا أردت أن تجعل العمود email يكون NOT NULL وأيضًا UNIQUE في نفس الوقت، يمكنك فعل ذلك عند إنشاء الجدول:

CREATE TABLE Students (
    id INT NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE, -- NULL لا يقبل
    age INT,
    level VARCHAR(50)
);

الآن العمود email لا يقبل NULL لأننا جعلناه NOT NULL وأيضًا لا يمكن أن يتكرر بسبب الـ UNIQUE


إذا كان لديك جدول موجود بالفعل وتريد إضافة UNIQUE لعمود معين
فنحن نستخدم الأمر ALTER TABLE لتعديل أي Constraint على الجدول
سواء حذف أو إضافة Constraint

ALTER TABLE Students
ADD CONSTRAINT unique_email UNIQUE (email);

هنا في الـ ALTER TABLE نحدد اسم الجدول Users
ثم نكتب الأمر ADD CONSTRAINT ثم نكتب اسم الـ Constraint الذي نريد إضافته، مثلًا هنا كتبنا unique_email وهذا اسم اختياري خاص باسم الـ Constraint
ثم نكتب UNIQUE (email) لتحديد العمود الذي نريد تطبيق الـ Constraint عليه

هكذا نكون قد أضفنا Constraint الـ UNIQUE على العمود email في جدول Users
واسم الـ Constraint هو unique_email، ويمكننا استخدامه لاحقًا إذا أردنا حذف أو تعديل هذا الـ Constraint
وهو اسم اختياري، يمكنك عدم كتابته وسيتم توليد اسم تلقائي للـ Constraint

ALTER TABLE Users
ADD CONSTRAINT UNIQUE (username);

هنا كتبنا ADD CONSTRAINT UNIQUE (username) دون تحديد اسم للـ Constraint
وبالتالي سيقوم نظام الـ DBMS الذي نستخدمه أيًا ما كان بتوليد اسم تلقائي Constraint مثل ck_users_username_1234 أو unique_username

وأيضًا في بعض أنظمة الـ DBMS يمكننا الاستغناء عن كلمة CONSTRAINT

ALTER TABLE Users
ADD UNIQUE (username);

هذه طرق مختلفة لإضافة UNIQUE إلى عمود في جدول موجود
والاسم كما قلنا مفيد في حالة إذا أردنا حذف الـ Constraint لاحقًا أو تعديله
فعلى سبيل المثال لو أردنا حذف Constraint الـ UNIQUE الذي أضفناه، يمكننا فعل ذلك باستخدام الأمر ALTER TABLE

ALTER TABLE Users
DROP CONSTRAINT unique_username;

هنا يجب ان نكتب اسم الـ Constraint الذي أضفناه سابقًا، وهو unique_username


وتذكر في حالة إذا أردت إضافة Constraint الـ UNIQUE فأنه لن ينجح إذا كان هناك قيم مكررة في العمود
بالتالي يجب عليك حذف أو تعديل القيم المكررة أولًا والتأكد من عدم وجود أي قيم مكررة في العمود ثم يمكنك إضافة Constraint الـ UNIQUE إلى العمود

الـ PRIMARY KEY

الـ PRIMARY KEY هو واحد من أهم المفاهيم في قواعد البيانات
يمكنك تخيله كـ "رقم الهوية" لكل صف في الجدول، بحيث لا يمكن أن يتكرر أبدًا

والـ PRIMARY KEY في الواقع هو مزيج بين الـ NOT NULL والـ UNIQUE

PRIMARY KEY = NOT NULL + UNIQUE

بمعنى أنه لا يمكن أن يكون فارغًا ولا يمكن أن يتكرر

ملحوظة: كل جدول له PRIMARY KEY واحد فقط، ولا يمكن أن يكون هناك أكثر من عمود PRIMARY KEY في نفس الجدول
لكن يمكن أن يملك الجدول أكثر من عمود UNIQUE

تخيل أن لديك جدول للطلاب، وهناك طالبان اسمهما "أحمد محمد محمود حمادة" وعمرهما 20 سنة ومن نفس المدينة
كيف ستميز بينهما؟ هنا يأتي دور الـ PRIMARY KEY
في الغالب نحن نستخدم عمودًا يسمى id ليكون الـ PRIMARY KEY، وهو رقم مختلف لكل طالب
بالتالي حتى لو كان هناك طالبان بنفس الاسم، يمكنك تمييزهما باستخدام id

CREATE TABLE Students (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT,
    level VARCHAR(50)
);

هنا العمود id هو الـ PRIMARY KEY، وهو ما يمثل "رقم الهوية" لكل طالب
وعندما ننظر إلى DESCRIBE Students;، سنرى أن العمود id يحمل علامة PRI، مما يعني أنه PRIMARY KEY

+-------+------------------+-------+-----+---------+----------------+
| Field | Type             | Null  | Key | Default | Extra          |
+-------+------------------+-------+-----+---------+----------------+
| id    | int              | NO    | PRI | NULL    |                |
| name  | varchar(100)     | NO    |     | NULL    |                |
| email | varchar(100)     | NO    | UNI | NULL    |                |
| age   | int              | YES   |     | NULL    |                |
| level | varchar(100)     | YES   |     | NULL    |                |
+-------+------------------+-------+-----+---------+----------------+

تذكر أن PRIMARY KEY هو مزيج من NOT NULL و UNIQUE
لذا سيضمن لنا هنا أن كل طالب لديه id مختلف ولا يمكن أن يكون فارغًا

ويمكنك أيضًا تحديد الـ PRIMARY KEY عند إنشاء الجدول باستخدام CREATE TABLE طريقة أخرى:

CREATE TABLE Students (
    id INT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT,
    level VARCHAR(50),

    CONSTRAINT pk_student PRIMARY KEY (id)
);

هنا استخدمنا CONSTRAINT pk_student PRIMARY KEY (id) لتحديد العمود id كـ PRIMARY KEY
مثل ما فعلنا مع الـ UNIQUE
أو يمكنك ببساطة كتابة:

CREATE TABLE Students (
    id INT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT,
    level VARCHAR(50),

    PRIMARY KEY (id)
);

هنا استخدمنا PRIMARY KEY (id) لتحديد العمود id كـ PRIMARY KEY وهذه الطريقة تدعمها بعض أنظمة الـ DBMS مثل MySQL


وأيضًا إذا كان لديك جدول موجود وتريد إضافة PRIMARY KEY لعمود معين، يمكنك فعل ذلك باستخدام الأمر ALTER TABLE:

ALTER TABLE Students
ADD CONSTRAINT pk_student PRIMARY KEY (id);

في هذه الحالة عليك التأكد من أن العمود id لا يحتوي على قيم NULL أو أن يكون يملك CONSTRAINT الـ NOT NULL

على أي حال في بعض الـ DBMS مثل MySQL، يتم تجاهل الاسم الذي تعطيه للـ PRIMARY KEY
لأنه يتم التعامل مع الـ PRIMARY KEY كحالة مميزة عن باقي الـ Constraints
بالتالي لا داعي لتحديد اسم للـ PRIMARY KEY، يمكنك ببساطة كتابة:

ALTER TABLE Students
ADD PRIMARY KEY (id);

ولو أردت حذف الـ PRIMARY KEY، يمكنك فعل ذلك باستخدام الأمر ALTER TABLE:

ALTER TABLE Students
DROP PRIMARY KEY;

لذا عليك الانتباه لهذه الفروقات عندما تتعامل مع أنظمة الـ DBMS المختلفة مثل MySQL أو PostgreSQL أو SQL Server

الـ Composite Primary Key

الـ Composite Primary Key هو نوع من الـ PRIMARY KEY يتكون من أكثر من عمود
بمعنى أنه مجرد PRIMARY KEY لكن قيمته يستمدها من أكثر من عمود

بمعنى تخيل أنك لديك جدول لتسجيل الطلاب في الدورات، حيث يمكن لكل طالب أن يسجل في أكثر من دورة
بالتالي سيكون لدينا جدول الـ Students و جدول الـ Courses
ونحتاج لجدول ثالث يربط بين الطلاب والدورات، وهو جدول الـ StudentCourses أو يمكنك تسميته Enrollments

شكل هذا الجدول سيكون كالتالي:

CREATE TABLE StudentCourses (
    student_id INT,
    course_id INT,
    enrollment_date DATE,

    PRIMARY KEY (student_id, course_id)
);

لاحظ هنا أن جدول StudentCourses يستخدم الـ PRIMARY KEY مركب من student_id و course_id
وهذا هو الـ Composite Primary Key

في هذه الحالة الـ PRIMARY KEY هو مزيج من student_id و course_id
بالتالي كل زوج من student_id و course_id يجب أن يكونا UNIQUE ولا يتكررا
بالتالي، فهذا يعني أن الطالب الواحد لا يمكن أن يسجل في نفس الدورة مرتين

دعونا نلقي نظرة على الـ DESCRIBE StudentCourses; ونرى كيف يبدو:

+----------------+------------------+-------+-----+---------+----------------+
| Field          | Type             | Null  | Key | Default | Extra          |
+----------------+------------------+-------+-----+---------+----------------+
| student_id     | int              | NO    | PRI | NULL    |                |
| course_id      | int              | NO    | PRI | NULL    |                |
| enrollment_date| date             | YES   |     | NULL    |                |
+----------------+------------------+-------+-----+---------+----------------+

هنا قد تظن أننا نلك اثنين من الـ PRIMARY KEY، وهذا ليس صحيحًا
لأننا لدينا Composite Primary Key يتكون من عمودين هما student_id و course_id
وليس لدينا PRIMARY KEY منفصل لكل عمود

تذكر أن أي جدول لا يمكن أن يحتوي على أكثر من PRIMARY KEY واحد

ولو أردت إثبات صحة ما أقول يمكنك تجربة الأمر التالي:

SELECT *
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_NAME = 'StudentCourses';

أعلم أننا لم نشرح الـ SELECT بعد لكن أظنك بمجرد النظر إلى هذا الأمر ستفهم أنه يحضر لنا كل شيء متعلق بالـ CONSTRAINTS الخاصة بجدول StudentCourses
من جدول أخر يدعى information_schema.TABLE_CONSTRAINTS
ومعظم أنظمة الـ DBMS تخزن معلومات داخلية لها في جداول خاصة بها داخل Database مثل information_schema

على أي حال، بعد تنفيذ هذا الأمر ستجد جدولًا يحتوي على كل الـ CONSTRAINTS الخاصة بجدول StudentCourses
والذي سيظهر لك أن هناك PRIMARY KEY واحد فقط وهو Composite Primary Key وليس اثنين

+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA    | TABLE_NAME      | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+
| def                | school            | PRIMARY         | school          | studentcourses  | PRIMARY KEY     | YES      |
+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+

ستجده يخبرك أن هناك جدول StudentCourses يحتوي على PRIMARY KEY واحد فقط
وبالمناسبة كلمة SCHEMA المقصود بها هنا هي Database في بعض أنظمة الـ DBMS مثل MySQL
لذا ستجد أن TABLE_SCHEMA هو school و TABLE_NAME هو studentcourses
لأننا أنشأنا جدول StudentCourses داخل قاعدة البيانات school
وتذكر إنشاء قاعدة البيانات school في مقالة ما هي لغة الـ SQL ؟


بالطبع سيأتي شخص ويقول يمكننا إضافة عمود id كـ PRIMARY KEY عادي، لكن هذا ليس منطقيًا في هذه الحالة
ونجعل الـ student_id و course_id يكونان UNIQUE بل يمكننا عمل Composite Unique Key أيضًا

CREATE TABLE StudentCourses (
    id INT PRIMARY KEY,
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    enrollment_date DATE,

    UNIQUE (student_id, course_id)
);

هكذا لدينا PRIMARY KEY منفصل هو id، وأيضًا لدينا UNIQUE مركب من student_id و course_id

وإذا نظرنا إلى المعلومات الموجودة في information_schema.TABLE_CONSTRAINTS الآن

+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA    | TABLE_NAME      | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+
| def                | school            | PRIMARY         | school          | studentcourses  | PRIMARY KEY     | YES      |
| def                | school            | UNIQUE          | school          | studentcourses  | UNIQUE          | YES      |
+--------------------+-------------------+-----------------+-----------------+-----------------+-----------------+----------+

ستجده أنه أصبح لدينا اثنين من الـ CONSTRAINTS، أما عندما كان لدينا Composite Primary Key كان لدينا CONSTRAINT واحد فقط
بالتالي المثال السابق يستخدم Constraint اقل لتحقيق نفس الهدف الخاص بالمثال الثاني
بالتالي يستهلك مساحة أقل وسرعة تنفيذ أعلى

لذا من الأفضل استخدام Composite Primary Key في هذه الحالة وليس استخدام PRIMARY KEY منفصل مع Composite Unique
وفيما بعد ستعرف أن الـ Constraint مثل PRIMARY KEY و UNIQUE يمكن أن يكون لهما تأثير على الأداء في بعض الحالات
لأنهما ينشئان INDEX والذي يساعد في تسريع عمليات البحث لكنه يستهلك مساحة أكبر في التخزين

سنتعرف على الـ INDEX وأنواعها في المقالات القادمة

الـ AUTO_INCREMENT

الـ AUTO_INCREMENT هو Constraint يستخدم لجعل العمود يتزايد تلقائيًا كلما أضفنا صفًا جديدًا
بالتالي لو لدينا جدول للطلاب ولدينا عمود يدعى student_number، يمكننا استخدام AUTO_INCREMENT لجعل هذا العمود يتزايد تلقائيًا
بالتالي عندما نضيف طالبًا جديدًا، لا نحتاج لتحديد قيمة student_number، بل سيقوم النظام بتوليدها تلقائيًا
فأول طالب سيأخذ student_number = 1، والثاني سيأخذ student_number = 2، وهكذا
هذا يجعلنا نتجنب الأخطاء البشرية في إدخال القيم التي قد تتكرر

ولاحظ أننا عندما تعاملنا مع الـ PRIMARY KEY في الأمثلة السابقة، كنا نحتاج دائمًا إلى إعطاء قيمة للعمود id

INSERT INTO Students (id, name, email, age, level)
VALUES (1, 'Ahmed Moustafa', '[email protected]', 20, 'Beginner');

لكن هذا يضع مسؤولية على المطور أو المستخدم لضمان أن قيمة الـ id لا تتكرر
وهذا يفتح المجال للأخطاء البشرية

تخيل أنك تقوم بإدخال طلاب جدد في النظام، وعن طريق الخطأ أعطيت نفس الـ id لطالبين مختلفين
هنا ستحصل على خطأ لأن الـ PRIMARY KEY لا يقبل القيم المكررة
وهذا يعني أنك ستحتاج إلى تذكر آخر id استخدمته في كل مرة تضيف طالب جديد

الحل هو الـ AUTO_INCREMENT

الـ AUTO_INCREMENT هو خاصية نضعها على عمود من نوع INT لجعله يتزايد تلقائيًا
بحيث في كل مرة تضيف صف جديد، يقوم النظام تلقائيًا بإعطاء الصف رقم id مختلف بدون تدخل منك

CREATE TABLE Students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT,
    level VARCHAR(50)
);

هنا جعلنا الـ id يكون AUTO_INCREMENT
بمعنى أننا رمينا مسؤولية إنشاء الـ id للـ Database بدلًا من أن نتدخل نحن

عندما تضيف الطالب الأول، سيأخذ id = 1 تلقائيًا
عندما تضيف الطالب الثاني، سيأخذ id = 2 تلقائيًا
وهكذا...

لنرى كيف يبدو هذا عند تنفيذ الأمر DESCRIBE Students;:

+-------+------------------+-------+-----+---------+----------------+
| Field | Type             | Null  | Key | Default | Extra          |
+-------+------------------+-------+-----+---------+----------------+
| id    | int              | NO    | PRI | NULL    | auto_increment |
| name  | varchar(100)     | NO    |     | NULL    |                |
| email | varchar(100)     | NO    | UNI | NULL    |                |
| age   | int              | YES   |     | NULL    |                |
| level | varchar(50)      | YES   |     | NULL    |                |
+-------+------------------+-------+-----+---------+----------------+

لاحظ أن الـ id الآن لديه معلومة جديدة في خانة الـ Extra وهي auto_increment
هذا يعني أن النظام سيتولى إنشاء القيم تلقائيًا

ملحوظة: الـ AUTO_INCREMENT يعمل فقط مع الأعمدة من نوع الأرقام مثل INT, BIGINT، وعادة ما يستخدم مع الـ PRIMARY KEY

الـ DEFAULT

أحيانًا تريد أن تضع قيمة افتراضية لعمود معين في حالة عدم تحديد قيمة له عند إدخال البيانات
هنا يأتي دور Constraint الـ DEFAULT الذي يسمح لك بتحديد قيمة افتراضية للعمود

في حالتنا لدينا جدول للطلاب وكل طالمة لديه عمود يدعى level يحدد مستوى الطالب سؤال Beginner أو Intermediate أو Advanced

هنا نحتاج لجعل القيمة الافتراضية للعمود level هي Beginner في حالة عدم تحديد مستوى الطالب عند إدخال البيانات
يمكننا فعل ذلك باستخدام DEFAULT:

CREATE TABLE Students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT,
    level VARCHAR(50) DEFAULT 'Beginner'
);

الآن كما تلاحظ أننا جعلنا العمود level يحمل قيمة افتراضية هي 'Beginner'
بالتالي إذا حاولنا إدخال طالب جديد بدون تحديد مستوى الطالب، سيأخذ العمود level القيمة الافتراضية 'Beginner'
لكن إذا حددنا مستوى الطالب، فسيأخذ القيمة التي حددناها

وبالطبع يمكنك رؤية ذلك عند استخدام DESCRIBE Students;:

+-------------------+------------------+-------+-----+----------------+----------------+
| Field             | Type             | Null  | Key | Default        | Extra          |
+-------------------+------------------+-------+-----+----------------+----------------+
| id                | int              | NO    | PRI | NULL           | auto_increment |
| name              | varchar(100)     | NO    |     | NULL           |                |
| email             | varchar(100)     | NO    | UNI | NULL           |                |
| age               | int              | YES   |     | NULL           |                |
| level             | varchar(50)      | YES   |     | Beginner       |                |
+-------------------+------------------+-------+-----+----------------+----------------+

لاحظ أن الأعمدة التي لها DEFAULT تظهر القيمة الافتراضية في خانة Default

الـ CHECK

هنا لدينا Constraint مميز قليلًا وهو الـ CHECK يسمح لك بوضع شرط معين على العمود، بحيث لا يتم قبول أي قيمة لا تحقق هذا الشرط
مثلًا، يمكنك التأكد من أن عمر الطالب أكبر من 13 أو أن قيمة العمود level هي واحدة من المستويات المعروفة مثل Beginner, Intermediate, أو Advanced
يمكننا استخدام CHECK لوضع هذه الشروط على الأعمدة عند إنشاء الجدول

CREATE TABLE Students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    age INT,
    level VARCHAR(50) DEFAULT 'Beginner',
    email VARCHAR(100) NOT NULL UNIQUE,

    CHECK (age >= 13 AND age <= 100),
    CHECK (level IN ('Beginner', 'Intermediate', 'Advanced'))
);

يمكنك أيضًا إنشاء CHECK مع اسم محدد:

CREATE TABLE Students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    age INT,
    level VARCHAR(50) DEFAULT 'Beginner',
    email VARCHAR(100) NOT NULL UNIQUE,

    CONSTRAINT chk_age CHECK (age >= 13 AND age <= 100),
    CONSTRAINT chk_level CHECK (level IN ('Beginner', 'Intermediate', 'Advanced'))
);

هكذا أصبح بعض الشيوط الصارمة على مستوى الجدول
منها أن العمود age يجب أن يكون بين 13 و 100
والعمود level يجب أن يكون واحد من المستويات Beginner, Intermediate, أو Advanced

وأعطينا اسم اختياري للـ CHECK وهو chk_age و chk_level

الآن إذا حاول أحد إدخال طالب عمره 10 سنوات أو مستوى Expert بهذا الشكل:

INSERT INTO Students (name, age, email)
VALUES ('Ali', 10, '[email protected]');

ستظهر رسالة خطأ تخبرنا أن القيمة غير مقبولة بسبب الـ CHECK التي وضعناها

SQL ERROR: Check constraint 'chk_age' is violated.

ولاحظ أنه استخدم اسم الـ CHECK الذي وضعناه وهو chk_age ليخبرنا أن الشرط الخاص بالعمر غير متحقق
ونفس الشيء إذا حاولنا إدخال مستوى Expert:

INSERT INTO Students (name, age, level, email)
VALUES ('Ayman', 30, 'Expert', '[email protected]');

ستظهر رسالة خطأ تخبرنا أن القيمة غير مقبولة بسبب الـ CHECK التي وضعناها على العمود level

SQL ERROR: Check constraint 'chk_level' is violated.

ملحوظة: في الشغل الحقيقي في الشركات لا يفضل أو غير مستحب استخدام CHECK في قاعدة البيانات ويفضل وضع تلك القيود او الشروط في التطبيق نفسه سواء في الـ Frontend أو الـ Backend

السبب يرجع إلى أن معظم الشروط تكون مرتبطة بطبيعة العمل أول بالـ Business Logic أو حتى بالـ Business Requirements
وأحيانًا الـ Business Requirements تتغير كل ليلة وأخرى وأحيانًا تكون معقدة ومتشابكة

بمعنى أنه في حالتنا هنا لدينا شرط أن الـ level يجب أن يكون واحد من المستويات Beginner, Intermediate, أو Advanced
لكن في حالة تغير الـ Business Requirements وأصبح لدينا مستوى جديد مثل Expert أو Pro
فهذا يعني أننا سنضطر لتعديل الـ Database والجداول لإضافة هذا المستوى الجديد
بالتالي سنحتاج لتعديل الـ CHECK الذي وضعناه على العمود level

بالتالي أمر الـ CHECK لا يتم استخدامه لأنك لا تريد أن تضطر لتعديل الجداول كلما تغير الـ Business
وبعض الأحيان تكون الشروط معقدة جدًا بحيث لا يمكن التعبير عنها بسهولة في SQL

لذا يفضل وضع تلك الشروط في التطبيق نفسه سواء في الـ Frontend أو الـ Backend دون الحاجة لتعديل الجداول في قاعدة البيانات

لكن بالطبع أنا اتحدث فقط عن الـ CHECK
أما بالنسبة للـ NOT NULL, UNIQUE, و PRIMARY KEY، فهي قيود أساسية يجب أن تكون موجودة في قاعدة البيانات لضمان سلامة البيانات

الـ FOREIGN KEY

الـ FOREIGN KEY هو واحد من أهم المفاهيم في قواعد البيانات
لأنه يستخدم لـ ربط جدولين معًا وضمان سلامة العلاقة بينهما

تتذكر عندما تخيلنا أننا لدينا جدول للطلاب وجدول للدورات ؟ جدول Students وجدول Courses
قلنا أن كل طالب يمكن أن يسجل في أكثر من دورة، وكل دورة يمكن أن يكون فيه أكثر من طالب
لذا قمنا بإنشاء جدول ثالث يربط بين الطلاب والدورات وهو جدول StudentCourses

CREATE TABLE StudentCourses (
    id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT,
    course_id INT,
    enrollment_date DATE,

    FOREIGN KEY (student_id) REFERENCES Students(id),
    FOREIGN KEY (course_id) REFERENCES Courses(id)
);

في جدول StudentCourses، لدينا عمودين هما student_id و course_id
عندما تصف العمود student_id فأنت تقول هو بمجرد id يشير إلى id في جدول Students
وعندما تصف العمود course_id فأنت تقول هو مجرد id يشير إلى id في جدول Courses

بالتالي يمكننا أن نقول أن student_id يشير إلى PRIMARY KEY في جدول Students
وبالمثل course_id يشير إلى PRIMARY KEY في جدول Courses

وهذا تمامًا ما يفعله الـ FOREIGN KEY
فالـ FOREIGN KEY هو key نملكه في الجدول يشير إلى PRIMARY KEY في جدول آخر

بالتالي في حالة جدول StudentCourses:

  • student_id هو FOREIGN KEY يشير إلى id في جدول Students
  • course_id هو FOREIGN KEY يشير إلى id في جدول Courses

ولكي نصف ونوضح هذا الترابط للـ DBMS، نستخدم FOREIGN KEY في تعريف الجدول
لأن الـ DBMS لن نستنتج هذه الرابطة بمجرد وجود الأعمدة بنفس الأسماء
لذا يجب أن نخبره بشكل صريح أن student_id هو FOREIGN KEY يشير إلى id في جدول Students
وأن course_id هو FOREIGN KEY يشير إلى id في جدول Courses

وهذا ما فعلناه في تعريف جدول StudentCourses

FOREIGN KEY (student_id) REFERENCES Students(id),
FOREIGN KEY (course_id) REFERENCES Courses(id)

داخل الـ CREATE TABLE نكتب FOREIGN KEY ثم نحدد العمود الذي نريد أن يكون FOREIGN KEY
ثم نكتب REFERENCES متبوعًا باسم الجدول الذي يشير إليه (Students) ثم العمود الذي يشير إليه في هذا الجدول (id)

ماذا يضمن الـ FOREIGN KEY ؟

  • لا يمكن إدخال student_id غير موجود في جدول Students
    بمعنى أنه لا يمكن أن يكون هناك student_id في جدول StudentCourses يشير إلى طالب غير موجود في جدول Students
  • لا يمكن إدخال course_id غير موجود في جدول Courses
    بمعنى أنه لا يمكن أن يكون هناك course_id في جدول StudentCourses يشير إلى دورة غير موجودة في جدول Courses
  • لا يمكن حذف طالب من جدول Students إذا كان مسجل في دورات
    بمعنى أنه لا يمكن حذف صف من جدول Students إذا كان هناك صف في جدول StudentCourses يشير إلى هذا الطالب
  • لا يمكن حذف دورة من جدول Courses إذا كان فيه طلاب مسجلين
    بمعنى أنه لا يمكن حذف صف من جدول Courses إذا كان هناك صف في جدول StudentCourses يشير إلى هذه الدورة
  • يساعدنا في عمليات البحث المعقدة وسنتعمق في هذا في المقالات القادمة وخصوصًا الـ JOIN

ملحوظة: يوجد أنواع مختلفة للربط بين الجداول مثل ONE TO ONE, ONE TO MANY, MANY TO MANY
لكن سنتعرف على هذه الأنواع في المقالات القادمة وأيضًا الـ FOREIGN KEY موضوع عميق وله قواعد كثيرة متعلقة بـ CASCADE DELETE, CASCADE UPDATE وغيرها
لكن لأننا لم ندخل بعد في أوامر الـ INSERT و SELECT وبالأخص الـ DELETE وطرق التعامل مع البيانات، فأنا لم أتطرق لهذه التفاصيل الآن
لكن لا تقلق سنتعمق في الـ FOREIGN KEY والعلاقات بين الجداول في المقالات القادمة

ملخص الـ SQL Constraints

الآن تعلمنا جميع أنواع قيود الـ SQL الأساسية، دعونا نلخصها:

الـ Constraint الوصف مثال
NOT NULL يمنع أن يكون العمود فارغًا name VARCHAR(100) NOT NULL
UNIQUE يضمن عدم تكرار القيم email VARCHAR(100) UNIQUE
PRIMARY KEY يمثل رقم الهوية لكل صف id INT PRIMARY KEY
AUTO_INCREMENT يجعل العمود يتزايد تلقائيًا id INT AUTO_INCREMENT
DEFAULT يضع قيمة افتراضية country VARCHAR(50) DEFAULT 'Egypt'
CHECK يتحقق من شرط معين age INT CHECK (age >= 10)
FOREIGN KEY يربط بين جدولين student_id INT FOREIGN KEY REFERENCES Students(id)

قد أكون تعمدت تفويت بعض التفاصيل في هذا المقال حتى لا يكون معقدًا جدًا
ولكن لا تقلق فكل شيء سنعود له بالتفصيل في المقالات القادمة
أو أنك سوف تكتشفها وتتعلم بنفسك فيما بعد سواء من هذه المقالات أو من مصادر أخرى

في المقالة القادمة، سنتعلم كيفية إدخال البيانات إلى الجداول باستخدام أمر INSERT
وكيفية استرجاع البيانات باستخدام أمر SELECT
وحينها ستفهم كيف تعمل هذه القيود عمليًا وأهميتها في حماية البيانات