انتقال للمقال
وقت القراءة: ≈ 15 دقيقة

تطبيق الـ Database Normalization بشكل عملي

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

المقدمة

في عالم قواعد البيانات، أحد أهم المفاهيم التي يجب على كل مطور أو محلل بيانات أن يتقنها هو مفهوم Database Normalization
وأحيانًا يكون أول شيء تتعلمه بمعنى أنك يمكنك دراسة الـ Database Normalization قبل أن تتعلم أي شيء عن الـ SQL أو الـ NoSQL
لأن الـ Normalization يهتم بكيفية تصميم وتنظيم البيانات بشكل صحيح في قاعدة البيانات
إذا كنت تعرف بعض المفاهيم الأساسية الخاصة بالـ Database والـ Constraints وبعض الأمور الأخرى فهذا جيد
لكن إذا لم تكن تعرفهاوأنت جديد في عالم قواعد البيانات فلا تقلق، سأحاول جعل الشرح خاليًا من الأمور العملية
لأن الـ Normalization بطبيعة الحال ليس مرتبط بـ SQL أو بـ NoSQL
بل هو مفهوم وفلسفة تنظيمية لتصميم قواعد البيانات بشكل عام

ما هو الـ Normalization ؟

الـ Database Normalization هى عملية منهجية لتنظيم البيانات في قاعدة البيانات
ومعنى عملية منهجية هو أننا لدينا خطوات وقواعد نتبعها بشكل متسلسل لكي نصل إلى التصميم الأمثل

والهدف بالطبع هو تقليل تكرار البيانات وتحسين الأداء الخاص بقواعد البيانات
وأهم شيء تسهيل التعامل معها في المستقبل وتنظيمها

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

+----------------+-------------+--------------------------+------------------+-----------------------+
| student_name   | student_city| email                    | courses          | teachers              |
+----------------+-------------+--------------------------+------------------+-----------------------+
| Ahmed Moustafa | Cairo       | ahmed@university.edu     | Computer Science | Dr. Ahmed             |
| Ahmed Moustafa | Cairo       | ahmed@university.edu     | AI, ML, DL       | Dr. Kamal             |
| Osama Ali      | Alex        | osama.ali@university.edu | Computer Science | Dr. Ahmed, Dr. Khaled |
| Osama Ali      | Alex        | osama.ali@university.edu | Data Structures  | Dr. Khaled, Dr. Kamal |
+----------------+-------------+--------------------------+------------------+-----------------------+

لو نظرت بعناية إلى الجدول السابق، ستلاحظ عدة مشاكل:

  • تكرار البيانات: بيانات الطلاب مثل الـ student_name و email مكررة في أكثر من صف
    بمعنى في كل صف يسجل فيه الطالب في دورة يجب أن نكتب بياناته كاملة مرة أخرى كالـ student_name و student_city و email
  • صعوبة التعديل: لو أراد الطالب تغيير بريده الإلكتروني، يجب علينا تعديله في كل الصفوف التي تحتوي على بياناته
  • لا يوجد PRIMARY KEY: كيف نميز بين صف وآخر ؟
    بمعنى أو اردنا امساك صف معين لتعديله أو حذفه، كيف سنميزه عن الصفوف الأخرى ؟
  • تكرار القيم: في عمود courses و teachers لدينا أكثر من قيمة في نفس الخانة مفصولة بفواصل
    لاحظ أن الطالب Ahmed Moustafa سجل في 3 دورات مختلفة وهم AI, ML, DL وتم تخزيهم في نفس الخانة كقيمة واحدة مفصولة بفاصلة
    والأمر نفسه مع عمود teachers الذي يحتوي على أكثر من معلم كقيمة واحدة في نفس الخانة

هذه المشاكل وغيرها تجعل من الجدول السابق غير منظم وغير عملي
ويصعب التعامل معه في المستقبل سواء في عمليات البحث أو التعديل أو الحذف أو الإضافة

هذه الأمور بالطبع تسبب مشاكل سواء في تكرار البيانات أو مشاكل أخرى تسمى Anomalies
هنا يأتي دور الـ Database Normalization بحيث يعطيك قواعد وخطوات منظمة لتقسيم هذه البيانات إلى جداول منفصلة وفق منهج محدد

مشاكل الـ Data Anomalies

قبل أن ندخل في تفاصيل الـ Normalization دعنا نفهم أولاً لماذا نحتاج إلى الـ Normalization ؟
أعلم أننا قلنا أن الهدف هو تقليل تكرار البيانات وتحسين الأداء وتنظيم البيانات وعرضنا مثال عملي على جدول غير منظم
لكن بغض النظر عن هذه الأمور، هناك سبب آخر مهم جدًا وهو تجنب مشاكل الـ Data Anomalies

الـ Data Anomalies هى مشاكل تحدث عند وجود تكرار في البيانات أو عندما لا يكون تصميم قاعدة البيانات منظمًا بشكل صحيح،أو بمعنى أخر لا نطبق الـ Normalization بشكل صحيح
وخصوصًا في حالة وجود Partial Dependency أو Transitive Dependency في الجداول وهى أمور سنشرحها عندما نصل إلى الـ 2NF و الـ 3NF في الأقسام القادمة في هذه المقالة

لنرجع إلى مثالنا الأول ونرى ما هي المشاكل التي قد نواجهها:

+----------------+-------------+--------------------------+------------------+-----------------------+
| student_name   | student_city| email                    | courses          | teachers              |
+----------------+-------------+--------------------------+------------------+-----------------------+
| Ahmed Moustafa | Cairo       | ahmed@university.edu     | Computer Science | Dr. Ahmed             |
| Ahmed Moustafa | Cairo       | ahmed@university.edu     | AI, ML, DL       | Dr. Kamal             |
| Osama Ali      | Alex        | osama.ali@university.edu | Computer Science | Dr. Ahmed, Dr. Khaled |
| Osama Ali      | Alex        | osama.ali@university.edu | Data Structures  | Dr. Khaled, Dr. Kamal |
+----------------+-------------+--------------------------+------------------+-----------------------+

Insertion Anomaly - مشكلة الإضافة

الـ Insertion Anomaly تحدث عندما نريد إضافة بيانات جديدة ولكن لا نستطيع بسبب وجود بيانات أخرى مرتبطة بها بمعنى أخرى، لنفترض أننا نريد إضافة دورة جديدة تدعى Python Programming مع معلم جديد يدعى Dr. Mohamed في جدولنا
المشكلة هنا أننا نحتاج إلى student_name و student_city و email لكي نضيف هذه الدورة، لكننا لا نملك أي طالب ليسجل في هذه الدورة حتى الآن

ولا نريد أن يبدو الجدول هكذا:

+--------------+--------------+-------+--------------------+-------------+
| student_name | student_city | email | courses            | teachers    |
+--------------+--------------+-------+--------------------+-------------+
| NULL         | NULL         | NULL  | Python Programming | Dr. Mohamed |
+--------------+--------------+-------+--------------------+-------------+

هكذا لدينا صفوف غير مكتملة البيانات وأعمدة مهمة بقيم NULL
وأيضًا في حالة كان student_name هو الـ PRIMARY KEY، فهنا لدينا مشكلة كبيرة لأن الـ PRIMARY KEY لا يمكن أن يحتوي على قيم NULL
بمعنى أخر سيكون لدينا جدول مشوه ولا يمكننا الاعتماد عليه

Update Anomaly - مشكلة التعديل

الـ Update Anomaly تحدث عندما نريد تعديل بيانات معينة ولكن بسبب التكرار، يجب علينا تعديلها في عدة أماكن، مما يزيد من احتمالية حدوث أخطاء
بمعنى أخرى، لو أراد الطالب Ahmed Moustafa تغيير بريده الإلكتروني من ahmed@university.edu إلى ahmed.new@university.edu، يجب علينا تعديله في كل الصفوف التي تحتوي على بياناته
والتأكد من أننا لم ننسى أي صف

المشكلة هنا تكمن في احتمالية النسيان الذي قد يؤدي إلى وجود بيانات غير متناسقة في الجدول

+----------------+--------------+--------------------------+------------------+-----------+
| student_name   | student_city | email                    | courses          | teachers  |
+----------------+--------------+--------------------------+------------------+-----------+
| Ahmed Moustafa | Alex         | ahmed.new@university.edu | Computer Science | Dr. Ahmed |
| Ahmed Moustafa | Cairo        | ahmed@university.edu     | AI, ML, DL       | Dr. Kamal |
+----------------+--------------+--------------------------+------------------+-----------+

لاحظ هنا أننا لدينا نفس الطالب Ahmed Moustafa مع بيانات مختلفة كل صف سواء في الـ student_city أو في الـ email
بسبب التكرار لقد نسينا تعديل بياناته في بعض الصفوف

Deletion Anomaly - مشكلة الحذف

الـ Deletion Anomaly تحدث عندما نحذف بيانات معينة ونفقد بيانات أخرى مهمة مرتبطة بها عن غير قصد
لاحظ أن الطالب Osama Ali مسجل في دورة Data Structures والذي يشرف عليها المعلم Dr. Khaled و Dr. Kamal
تخيل أن الطالب Osama Ali قرر الانسحاب من دورة الـ Data Structures
بالتالي سنقوم بحذف الصف الخاص بتسجيله في هذه الدورة

+----------------+--------------+--------------------------+------------------+-----------------------+
| student_name   | student_city | email                    | courses          | teachers              |
+----------------+--------------+--------------------------+------------------+-----------------------+
| Ahmed Moustafa | Cairo        | ahmed@university.edu     | Computer Science | Dr. Ahmed             |
| Ahmed Moustafa | Cairo        | ahmed@university.edu     | AI, ML, DL       | Dr. Kamal             |
| Osama Ali      | Alex         | osama.ali@university.edu | Computer Science | Dr. Ahmed, Dr. Khaled |
+----------------+--------------+--------------------------+------------------+-----------------------+

لاحظ المشكلة أننا فقدنا معلومات الدورة Data Structures بالكامل بما في ذلك معلومات عن المعلم Dr. Khaled الذي كان يشرف على هذه الدورة
هكذا عندما نحذف تسجيل الطالب Osama Ali من دورة Data Structures، فقدنا أيضًا بيانات مهمة مرتبطة بالمعلم
وإذا كان Osama Ali هو الطالب الوحيد المسجل في هذه الدورة، فقد حذفنا كل أثر لوجود هذه الدورة في قاعدة البيانات


كما ترى، هذه المشاكل تجعل من الجدول غير عملي وصعب التعامل معه
وهنا يأتي دور الـ Database Normalization الذي يساعدنا في تنظيم البيانات بشكل صحيح وتجنب هذه المشاكل

مستويات الـ Normalization

لدينا عدة مستويات من الـ Normalization كل مستوى يحل مشاكل معينة ويجعل التصميم أفضل

  • First Normal Form (1NF): الصيغة الأولى
  • Second Normal Form (2NF): الصيغة الثانية
  • Third Normal Form (3NF): الصيغة الثالثة
  • Boyce-Codd Normal Form (BCNF): صيغة بويس-كود
  • Fourth Normal Form (4NF): الصيغة الرابعة
  • Fifth Normal Form (5NF): الصيغة الخامسة

كل صيغة أو مستوى لديها قواعدها الخاصة التي يجب اتباعها
وكل مستوى يعتمد على المستوى السابق
بمعنى لا يمكنك تطبيق قواعد المستوى 2NF إذا لم يكن الجدول في 1NF أولًا
فيجب أن تكون قد طبقت قواعد الـ 1NF قبل الانتقال إلى 2NF وهكذا مع باقي المستويات

في هذه المقالة سنشرح الصيغ الثلاثة الأولى 1NF و 2NF و 3NF
وفي المقالة التالية استكمال مستويات الـ Database Normalization سنشرح الصيغ المتقدمة BCNF و 4NF و 5NF

الصيغة الأولى First Normal Form

الـ First Normal Form أو اختصارًا نقول 1NF وهى الصيغة أو الشكل الأول الذي يركز على التخلص من التكرار سواء تكرار في القيم داخل خانات الصفوف أو تكرار في الأعمدة

قواعد الـ 1NF

  1. كل خانة تحتوي على قيمة واحدة فقط بمعنى أنه لا يجب أن يكون هناك قيم متعددة في الخانة الواحدة
  2. لا توجد أعمدة مكررة بمعنى أنه لا يجب أن يكون هناك أعمدة متكررة مثل course_1، course_2، course_3
  3. كل صف نقدر نميزه عن باقي الصفوف بمعنى أنه يجب أن يكون هناك PRIMARY KEY يميز كل صف
  4. الترتيب لا يهم بمعنى أنه لا يهم ترتيب الصفوف أو الأعمدة في الجدول، بمعنى أخر أننا لا نأخذ ترتيب الصفوف أو الأعمدة كبيانات ونبني عليها معلومات

الـ 1NF هو الخطوة الأولى نحو تنظيم البيانات بشكل صحيح
وكما ترى فهو بسيط نسبيًا لكنه مهم جدًا

مثال عملي على الـ 1NF

لنرى كيف نطبق الـ 1NF على مثال عملي

+----------------+------------------+-----------------+-----------------------+
| student_name   | course_1         | course_2        | teachers              |
+----------------+------------------+-----------------+-----------------------+
| Ahmed Moustafa | Computer Science | Web Development | Dr. Ahmed, Dr. Kamal  |
| Osama Ali      | Computer Science | Data Structures | Dr. Ahmed, Dr. Khaled |
| Ahmed Moustafa | Computer Science | NULL            | Dr. Ahmed             |
+----------------+------------------+-----------------+-----------------------+

في الجدول السابق، نلاحظ أنننا لدينا أعمدة مكررة course_1 و course_2
وأيضًا في عمود teachers لدينا أكثر من قيمة في الصف الواحد
وبالإضافة إلى ذلك، لا يوجد PRIMARY KEY يميز بين الصفوف

التكرار بشكل عام بهذا الشكل يجعل التعامل مع الجدول صعبًا وغير عملي
بحيث أننا لو أردنا التعديل أو الحذف أو الإضافة، سنواجه مشاكل كثيرة

لحل هذه المشاكل علينا تطبيق قواعد الـ 1NF وهى إزالة الأعمدة المكررة بحيث نجعل كل دورة في صف منفصل
ثم يجب علينا فصل القيم المتعددة في كل خانة بحيث نجعل كل معلم في صف منفصل
ثم أخيرًا إضافة PRIMARY KEY لتمييز الصفوف عن بعضها البعض
سواء عن طريق اختيار عمود موجود يمكنه أن يكون الـ PRIMARY KEY أو عن طريق إضافة عمود جديد فريد لكل صف وليكن id

وإذا كنت لا تعرف ما هو الـ PRIMARY KEY فهو ببساطة عمود نستخدمه لتمييز كل صف عن الآخر
بحيث لا يوجد صفين لهما نفس قيمة الـ PRIMARY KEY
ويمكنك قراءة المقال التالي لمعرفة المزيد عن الـ Primary Key ما هى أنواع الـ SQL Constraints المختلفة

بعد تطبيق هذه التعديلات، سيصبح الجدول كالتالي:

+----+----------------+------------------+------------+
| id | student_name   | course           | teacher    |
+----+----------------+------------------+------------+
| 1  | Ahmed Moustafa | Computer Science | Dr. Ahmed  |
| 2  | Ahmed Moustafa | Web Development  | Dr. Kamal  |
| 3  | Osama Ali      | Computer Science | Dr. Ahmed  |
| 4  | Osama Ali      | Data Structures  | Dr. Khaled |
| 5  | Ahmed Moustafa | Computer Science | Dr. Ahmed  |
+----+----------------+------------------+------------+

لاحظ أن الجدول أصبح أوضح وسهل التعامل معه وهو الآن يحقق الـ 1NF بشكل كامل
بحيث أن كل صف يمثل تسجيل طالب في دورة مع معلم معين
عن طريق أزالة التكرار في الأعمدة وفصل القيم المتعددة في الخانات
وأيضًا أضفنا PRIMARY KEY لتمييز الصفوف

ولو لاحظت فستجد أننا لدينا صفين مكررين للطالب Ahmed Moustafa في نفس الدورة Computer Science مع نفس المعلم Dr. Ahmed لسبب ما قد يكون تسجيله مرتين في نفس الدورة في أوقات مختلفة
لكننا أضفنا عمود id لنميز كل صف عن الآخر
هذا هو الـ First Normal Form ببساطة

الصيغة الثانية Second Normal Form

الـ Second Normal Form أو اختصارًا نقول 2NF وهى الصيغة أو الشكل الثاني الذي يركز على الأعتماد الكامل على الـ PRIMARY KEY
بحيث أن الـ 2NF يهدف إلى التخلص من الأعمدة التي تعتمد على جزء فقط من الـ PRIMARY KEY في حالة كان الـ PRIMARY KEY مركب من أكثر من عمود
وهو ما نسميه بالـ Partial Dependency

قواعد الـ 2NF

يمكننا تلخيص قواعد الـ 2NF كالتالي:

  1. يجب أن يكون الجدول في صيغة الـ 1NF وهذا من البديهيات بحيث كل صيغة تعتمد على الصيغة السابقة
  2. كل عمود في الجدول يجب أن يعتمد بشكل كامل على الـ PRIMARY KEY
  3. لا يوجد اعتماد جزئي أو ما يسمى بالـ Partial Dependency

أعرف أنك ستألني الآن وتقول ما معنى الـ Partial Dependency ؟

الـ Partial Dependency

يمكننا أن نقول أن الـ Partial Dependency هى أن بعض الأعمدة في الجدول تعتمد على جزء من الـ PRIMARY KEY وليس على الـ PRIMARY KEY بأكمله في حالة كان الـ PRIMARY KEY مركب من أكثر من عمود وهو ما نسميه بالـ Composite Primary Key

بمعنى أننا لو افترضنا أن العمودين A و B يشكلان الـ PRIMARY KEY المركب للجدول
ولدينا عمود C يعتمد فقط على العمود A وليس على العمود B
هنا نقول أن العمود C لديه Partial Dependency على الـ PRIMARY KEY

ونمثل هذا الترابط بهذا الشكل:

{B} -> {C}

هكذا نقول أن العمود C يعتمد على B و ليس على الـ PRIMARY KEY بأكمله {A, B}

بمعنى آخر، لو كان لدينا PRIMARY KEY مركب من عمودين مثل student_id و course_id
وكان لدينا بعض الأعمدة مثل student_name و student_email و course_name و course_duration و grade و enrollment_date

عندما نقوم بتحليل هذه الأعمدة ونرسم الترابط بينها وبين الـ PRIMARY KEY ستجدها كالتالي:

{student_id, course_id} -> {grade}
{student_id, course_id} -> {enrollment_date}

{student_id} -> {student_name}
{student_id} -> {student_email}

{course_id} -> {course_name}
{course_id} -> {course_duration}

ستلاحظ أن:

  • student_name و student_email يعتمدان فقط على student_id وليس على course_id
  • course_name و course_duration يعتمدان فقط على course_id وليس على student_id
  • grade و enrollment_date تعتمدان على كلا العمودين student_id و course_id

هكذا نقول أن student_name و student_email و course_name و course_duration لديهم Partial Dependency على الـ PRIMARY KEY
أي هذه الأعمدة تعتمد على جزء فقط من الـ PRIMARY KEY وليس على الـ PRIMARY KEY بأكمله
أما الأعمدة grade و enrollment_date فهي تعتمد على الـ PRIMARY KEY بأكمله

لاحظ أيضًا أن الأعمدة التي تعتمد على جزء فقط من الـ PRIMARY KEY يمكن فصلها في جداول مستقلة
بحيث نضع الأعمدة student_name و student_email في جدول يدعى Students
ونضع الأعمدة course_name و course_duration في جدول يدعى Courses
ونبقي الأعمدة grade و enrollment_date كما هى في جدول الـ Enrollments

مثال عملي على الـ 2NF

لنرى مثال عملي لتطبيق الـ 2NF

+----+----------------+------------------+------------+
| id | student_name   | course           | teacher    |
+----+----------------+------------------+------------+
| 1  | Ahmed Moustafa | Computer Science | Dr. Ahmed  |
| 2  | Ahmed Moustafa | Web Development  | Dr. Kamal  |
| 3  | Osama Ali      | Computer Science | Dr. Ahmed  |
| 4  | Osama Ali      | Data Structures  | Dr. Khaled |
| 5  | Ahmed Moustafa | Computer Science | Dr. Ahmed  |
+----+----------------+------------------+------------+

هذا الجدول هو المثال الذي استخدمناه مع الـ 1NF لكن هنا نحن لا نملك PRIMARY KEY مركب
والـ 2NF يتعامل مع الجداول التي لديها PRIMARY KEY مركب من أكثر من عمود
لذا سنغير الجدول قليلًا ليصبح لدينا PRIMARY KEY مركب من عمودين، لكي نتمكن من شرح الـ 2NF بشكل أفضل

+----------------+-----------------------+-----------------+-----------------+-----------------+
| student_name   | course_name           | teacher_name    | course_duration | enrollment_date |
+----------------+-----------------------+-----------------+-----------------+-----------------+
| Ahmed Moustafa | Database Fundamentals | Dr. Ahmed       | 50              | 2025-12-15      |
| Ahmed Moustafa | Web Development       | Dr. Kamal       | 30              | 2025-12-20      |
| Osama Ali      | Database Fundamentals | Dr. Ahmed       | 50              | 2025-12-15      |
| Osama Ali      | Data Structures       | Dr. Khaled      | 40              | 2025-12-25      |
+----------------+-----------------------+-----------------+-----------------+-----------------+

هنا الـ PRIMARY KEY هو مركب من عمودين student_name و course_name
بمعنى أن كل طالب يستطيع التسجيل في دورة واحدة فقط مرة واحدة
لأن من خصائص الـ PRIMARY KEY أنه لا يمكن أن يكون هناك صفين لهما نفس قيمة الـ PRIMARY KEY

على أي حال، لنحلل الأعمدة في الجدول
ستلاحظ أن الأعمدة teacher_name و course_duration تعتمد فقط على course_name وليس على student_name
بالتالي لدينا Partial Dependency في هذه الأعمدة
ولذلك يجب علينا فصل هذه الأعمدة في جدول منفصل

لما نفعل هذا ؟

لأن مع وجود الـ Partial Dependency في الجدول، قد نواجه مشاكل الـ Data Anomalies التي شرحناها في البداية
وهى أننا لو أردنا حذف دورة معينة، قد نفقد بيانات عن المعلم وهذا يعد Deletion Anomaly
ولو أردنا إضافة دورة جديدة بدون طالب، لن نستطيع لأن الجدول مرتبط بالطلاب وهذا يعد Insertion Anomaly
ولو أردنا تعديل بيانات المعلم مثل اسمه، يجب علينا تعديله في كل الصفوف في حالة كان المعلم يشرف على أكثر من دورة وهذا يعد Update Anomaly

على أي حال، لحل هذه المشاكل عن طريق فصل الأعمدة teacher_name و course_duration في جدول منفصل كما قلنا

لنفصلهم في جدول يدعى Courses

+---+-----------------------+-----------------+--------------+
|id | name                  | course_duration | teacher_name |
+---+-----------------------+-----------------+--------------+
| 1 | Database Fundamentals | 50              | Dr. Ahmed    |
| 2 | Web Development       | 30              | Dr. Kamal    |
| 3 | Data Structures       | 40              | Dr. Khaled   |
+---+-----------------------+-----------------+--------------+

والآن في جدول الـ Enrollments سنتخلص من الأعمدة teacher_name و course_duration
ونستبدل عمود course_name بـ course_id الذي سيكون الـ FOREIGN KEY الذي يربط بين جدول الـ Enrollments وجدول الـ Courses

+----------------+-----------+-----------------+
| student_name   | course_id | enrollment_date |
+----------------+-----------+-----------------+
| Ahmed Moustafa | 1         | 2025-12-15      |
| Ahmed Moustafa | 2         | 2025-12-20      |
| Osama Ali      | 1         | 2025-12-15      |
| Osama Ali      | 3         | 2025-12-25      |
+----------------+-----------+-----------------+

الآن الجدولين Courses و Enrollments في 2NF

بالطبع في حالة لدينا أعمدة تعتمد على student_name فقط مثل student_email و student_phone فهنا يجب علينا أيضًا فصل هذه الأعمدة في جدول منفصل يدعى Students
لنتظاهر أننا بالفعل لدينا أعمدة إضافية في جدول الـ Enrollments مثل student_email و student_phone
هكذا سيكون هذه الأعمدة تعتمد فقط على student_name وليس على course_id في الـ PRIMARY KEY المركب الخاصة بجدول الـ Enrollments
فهنا يجب علينا فصل هذه الأعمدة في جدول منفصل ولنسميه Students

+---+----------------+--------------------------+--------------+
|id | name           | email                    | phone        |
+---+----------------+--------------------------+--------------+
| 1 | Ahmed Moustafa | ahmed@university.edu     | 01012345678  |
| 2 | Osama Ali      | osama.ali@university.edu | 01098765432  |
+---+----------------+--------------------------+--------------+

والآن في جدول الـ Enrollments سنستبدل عمود student_name بـ student_id الذي سيكون الـ FOREIGN KEY الذي يربط بين جدول الـ Enrollments وجدول الـ Students

+------------+-----------+-----------------+
| student_id | course_id | enrollment_date |
+------------+-----------+-----------------+
| 1          | 1         | 2025-12-15      |
| 1          | 2         | 2025-12-20      |
| 2          | 1         | 2025-12-15      |
| 2          | 3         | 2025-12-25      |
+------------+-----------+-----------------+

الآن لدينا ثلاث جداول منفصلة:

  • جدول Students يحتوي على بيانات الطلاب
  • جدول Courses يحتوي على بيانات الدورات
  • جدول Enrollments يحتوي على بيانات التسجيل

كل هذه الجداول تحقق الـ 2NF بشكل كامل
لاحظ كيف أصبحت البيانات منظمة بشكل أفضل وسهل التعامل معها

الصيغة الثالثة Third Normal Form

الـ 3NF هو المستوى الثالث من الـ Normalization وهو يشبه الـ 2NF لكن يركز على زاوية مختلفة قليلاً

قواعد الـ 3NF

عندما تقرأ عن الـ 3NF ستجد أنه يقول لك أنه يجب أن نتخلص من الـ Transitive Dependency
وهو ما ستراه مكتوب في القواعد الـ 3NF التي تقول:

  1. يجب أن يكون الجدول في صيغة الـ 2NF لأنه كما قلنا أن كل صيغة تعتمد على الصيغة السابقة
  2. لا يوجد عمود يعتمد على الـ PRIMARY KEY من خلال عمود آخر وهو ما يسمى بالـ Transitive Dependency

أعرف أنك ستسألني مجددًا وتقول ما هو الـ Transitive Dependency ؟

الـ Transitive Dependency

حسنًا، الـ Transitive Dependency هو عندما نملك بعض الأعمدة التى لا تعتمد مباشرة على الـ PRIMARY KEY ولكنها تعتمد على عمود آخر يعتمد على الـ PRIMARY KEY
بمعنى أننا لدينا عمود A يعتمد على عمود B وعمود B يعتمد على الـ PRIMARY KEY
بالتالي نقول أن العمود A لديه Transitive Dependency على الـ PRIMARY KEY من خلال العمود B

لنرى مثال عملي لتوضيح هذا المفهوم

لنتذكر جدول الـ Courses الذي أنشأناه في الـ 2NF

+---+-----------------------+-----------------+--------------+-------------------+---------------+
|id | name                  | course_duration | teacher_name | teacher_email     | teacher_phone |
+---+-----------------------+-----------------+--------------+-------------------+---------------+
| 1 | Database Fundamentals | 50              | Dr. Ahmed    | dr.ahmed@uni.edu  | 01012345678   |
| 2 | Web Development       | 30              | Dr. Kamal    | dr.kamal@uni.edu  | 01098765432   |
| 3 | Data Structures       | 40              | Dr. Khaled   | dr.khaled@uni.edu | 01011223344   |
+---+-----------------------+-----------------+--------------+-------------------+---------------+

لاحظ هنا أنني أضفت أعمدة إضافية خاصة بالمعلم مثل teacher_email و teacher_phone لغرض المثال فقط

هنا إذا سألنا أنفسنا، هل العمودين teacher_email و teacher_phone تعتمد بشكل مباشرة على الـ PRIMARY KEY الذي هو id في هذا الجدول ؟
لنرسم الترابط بينها وبين الـ PRIMARY KEY لكي نستطيع الإجابة على هذا السؤال:

{id} -> {name}
{id} -> {course_duration}
{id} -> {teacher_name}

{teacher_name} -> {teacher_email}
{teacher_name} -> {teacher_phone}

ستلاحظ أن العمودين teacher_email و teacher_phone يتعلقان بالمعلم teacher_name وليس بالـ PRIMARY KEY بشكل مباشر
بمعنى أننا نستطيع معرفة قيم العمودين teacher_email و teacher_phone من خلال معرفة اسم المعلم teacher_name فقط
وليس من خلال معرفة الـ id الخاص بالدورة

بالتالي، نستطيع القول أن العمودين teacher_email و teacher_phone يعتمدان على عمود teacher_name فقط وليس على الـ PRIMARY KEY
وطالما أن teacher_name يعتمد على الـ PRIMARY KEY فنقول حينها أن كلا teacher_email و teacher_phone لديهما Transitive Dependency على الـ PRIMARY KEY من خلال teacher_name

ونستطيع أن نمثل الـ Transitive Dependency بهذا الشكل:

{id} -> {teacher_name} -> {teacher_email}
{id} -> {teacher_name} -> {teacher_phone}

وبالطبع إذا كان لدينا أعمدة أخرى خاصة بالمعلم مثل teacher_phone أو teacher_office فهى أيضًا لديها Transitive Dependency على الـ PRIMARY KEY من خلال teacher_name

لما هذا مهم ؟

لأن الـ Transitive Dependency يخلق مشاكل مشابهة لمشاكل الـ Partial Dependency التي ذكرناها في الـ 2NF
وهى مشاكل الـ Data Anomalies، بحيث أننا لو حذفنا صف معين في جدول الـ Courses، قد نفقد بيانات عن المعلم وهذا يعد Deletion Anomaly
ولو أردنا إضافة معلم جديد بدون دورة، لن نستطيع لأن الجدول مرتبط بالدورات وهذا يعد Insertion Anomaly
ولو أردنا تعديل بيانات المعلم مثل بريده الإلكتروني، يجب علينا تعديله في كل الصفوف في حالة كان المعلم يشرف على أكثر من دورة وهذا يعد Update Anomaly

مثال عملي على الـ 3NF

لنرى كيف نطبق هذه القواعد على مثالنا العملي
قلنا أن كلا العمودين teacher_email و teacher_phone لديهم Transitive Dependency على الـ PRIMARY KEY من خلال teacher_name
لذا سنقوم بفصل هذه الأعمدة في جدول منفصل يدعى Teachers

+---+-----------------+-------------------+-------------+
|id | name            | email             | phone       |
+---+-----------------+-------------------+-------------+
| 1 | Dr. Ahmed       | dr.ahmed@uni.edu  | 01012345678 |
| 2 | Dr. Kamal       | dr.kamal@uni.edu  | 01098765432 |
| 3 | Dr. Khaled      | dr.khaled@uni.edu | 01011223344 |
+---+-----------------+-------------------+-------------+

والآن في جدول الـ Courses سنقوم بحذف الأعمدة teacher_email و teacher_phone
وسنستبدل العمود الـ teacher_name بـ teacher_id الذي سيكون الـ FOREIGN KEY الذي يربط بين جدول الـ Courses وجدول الـ Teachers

+---+-----------------------+-----------------+------------+
|id | name                  | course_duration | teacher_id |
+---+-----------------------+-----------------+------------+
| 1 | Database Fundamentals | 50              | 1          |
| 2 | Web Development       | 30              | 2          |
| 3 | Data Structures       | 40              | 3          |
+---+-----------------------+-----------------+------------+

الآن كلا الجدولين Courses و Teachers في 3NF لاحظ كيف أصبحت البيانات منظمة بشكل أفضل وسهل التعامل معها
الآن نستطيع التعامل مع بيانات المعلمين بشكل مستقل عن الدورات التي يشرفون عليها
ونستطيع إضافة معلم جديد بدون الحاجة إلى ربطه بدورة معينة
ونستطيع تعديل بيانات المعلم بسهولة دون الحاجة إلى تعديل عدة صفوف في جدول الدورات
ونستطيع أيضًا حذف جميع الدورات دون القلق من فقدان بيانات المعلمين


أظن أن الـ 3NF كان سهلًا جدًا لدرجة أنك يمكنك معرفته وتطبيقه بسهولة بمجرد النظر
على أي حال في معظم الحالات، الوصول إلى الـ 3NF يكون كافيًا جدًا لتصميم قاعدة بيانات منظمة وعملية

ملخص مستويات الـ Normalization الأساسية

لنلخص ما تعلمناه في هذه المقالة:

الصيغة الهدف الأساسي المشكلة التي تحلها
1NF التخلص من التكرار في القيم والأعمدة قيم متعددة في خانة واحدة أو أعمدة مكررة
2NF التخلص من الـ Partial Dependency أعمدة تعتمد على جزء من الـ PRIMARY KEY
3NF التخلص من الـ Transitive Dependency أعمدة تعتمد على الـ PRIMARY KEY بشكل غير مباشر

في معظم الحالات العملية، الوصول إلى الـ 3NF يكون كافيًا جدًا لتصميم قاعدة بيانات منظمة وعملية

لكن هناك صيغ متقدمة أخرى مثل BCNF و 4NF و 5NF تتعامل مع حالات خاصة ونادرة
يمكنك قراءة المزيد عنها في المقالة استكمال مستويات الـ Database Normalization

أتمنى أن تكون هذه المقالة قد ساعدتك في فهم مفهوم الـ Database Normalization بشكل أفضل
وأن تكون الآن قادرًا على تطبيق هذه القواعد في تصميم قواعد البيانات الخاصة بك