تطبيق الـ 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
- كل خانة تحتوي على قيمة واحدة فقط بمعنى أنه لا يجب أن يكون هناك قيم متعددة في الخانة الواحدة
- لا توجد أعمدة مكررة بمعنى أنه لا يجب أن يكون هناك أعمدة متكررة مثل
course_1،course_2،course_3 - كل صف نقدر نميزه عن باقي الصفوف بمعنى أنه يجب أن يكون هناك
PRIMARY KEYيميز كل صف - الترتيب لا يهم بمعنى أنه لا يهم ترتيب الصفوف أو الأعمدة في الجدول، بمعنى أخر أننا لا نأخذ ترتيب الصفوف أو الأعمدة كبيانات ونبني عليها معلومات
الـ 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 كالتالي:
- يجب أن يكون الجدول في صيغة الـ
1NFوهذا من البديهيات بحيث كل صيغة تعتمد على الصيغة السابقة - كل عمود في الجدول يجب أن يعتمد بشكل كامل على الـ
PRIMARY KEY - لا يوجد اعتماد جزئي أو ما يسمى بالـ
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_idcourse_nameوcourse_durationيعتمدان فقط علىcourse_idوليس علىstudent_idgradeو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 التي تقول:
- يجب أن يكون الجدول في صيغة الـ
2NFلأنه كما قلنا أن كل صيغة تعتمد على الصيغة السابقة - لا يوجد عمود يعتمد على الـ
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 بشكل أفضل
وأن تكون الآن قادرًا على تطبيق هذه القواعد في تصميم قواعد البيانات الخاصة بك