خانه/ طراحی پایگاه‌داده/ نرمال‌سازی

نرمال‌سازی: چرا داده‌ی تکراری خطرناک است

متوسط ۱۶ دقیقه مطالعه

در فصل‌های قبل، همیشه با جدول‌های از‌قبل‌طراحی‌شده (مثل students، courses و enrollments) کار کردیم. اما این جدول‌ها را چه کسی و چگونه این‌طور طراحی کرده؟ اگر همه‌چیز را در یک جدول بزرگ می‌ریختیم چه می‌شد؟ نرمال‌سازی فرآیندی است که به ما کمک می‌کند تشخیص دهیم داده باید به چند جدول تقسیم شود، و دقیقاً کجا.

مشکل: یک جدول تخت (Flat) و پر از تکرار

فرض کن به‌جای سه جدول جدا، همه‌چیز را در یک جدول واحد می‌ریختیم:

idstudent_namecourse_titleteacher_name
1علیریاضیدکتر کاظمی
2زهراریاضیدکتر کاظمی
3محمدفیزیکدکتر رستمی

مشکل این‌جاست: نام درس «ریاضی» و نام استادش «دکتر کاظمی» در هر ردیفی که یک دانش‌آموز جدید در آن درس ثبت‌نام می‌کند، دوباره تکرار می‌شود. بیا ببینیم این تکرار دقیقاً چه بلایی سرمان می‌آورد:

امتحانش کن — ناهنجاری به‌روزرسانی (Update Anomaly)
حالا یک درسِ «ریاضی» دو استاد متفاوت دارد! ردیف ۱ می‌گوید استادش «دکتر احمدی» است، ردیف ۲ هنوز می‌گوید «دکتر کاظمی» — با این‌که هر دو باید همان یک درس با همان یک استاد باشند. این دقیقاً همان چیزی است که به آن ناهنجاری به‌روزرسانی (Update Anomaly) می‌گویند: چون یک واقعیت (نام استاد یک درس) در چند جای مختلف تکرار شده، به‌روزرسانی ناقص باعث تناقض می‌شود.

علاوه بر این، دو مشکل دیگر هم داریم:

  • ناهنجاری درج (Insert Anomaly): اگر بخواهیم یک درس جدید («شیمی») ثبت کنیم که هنوز هیچ دانش‌آموزی در آن ثبت‌نام نکرده، در این ساختار اصلاً جایی برای ثبتش نداریم — چون هر ردیف باید حتماً یک دانش‌آموز هم داشته باشد.
  • ناهنجاری حذف (Delete Anomaly): اگر تنها دانش‌آموز یک درس، ثبت‌نامش را لغو کند، اطلاعات آن درس (و حتی استادش) هم به‌طور کامل از دست می‌رود — چون جایی جدا برای نگه‌داشتنش وجود ندارد.

فرم نرمال اول (1NF): مقادیر اتمی

اولین قدم نرمال‌سازی: هر ستون باید فقط یک مقدار اتمی (تجزیه‌نشدنی) داشته باشد — نه لیستی از چند مقدار در یک خانه. برای مثال، ستونی مثل courses = 'ریاضی, فیزیک' در یک خانه، نقض 1NF است؛ باید هر درس در ردیف جدای خودش باشد.

فرم نرمال دوم (2NF): بدون وابستگی جزئی

2NF فقط وقتی معنا پیدا می‌کند که کلید اصلی ترکیبی باشد (یادت هست از درس کلید اصلی؟). می‌گوید: هر ستون غیرکلیدی باید به کل کلید ترکیبی وابسته باشد، نه فقط به بخشی از آن. مثلاً اگر کلید ترکیبی (student_id, course_id) باشد، ستونی مثل teacher_name که فقط به course_id وابسته است (نه به دانش‌آموز)، باید به جدول courses منتقل شود.

فرم نرمال سوم (3NF): بدون وابستگی تراگذر

3NF می‌گوید: ستون‌های غیرکلیدی نباید به ستون‌های غیرکلیدیِ دیگر وابسته باشند. در مثال ما، teacher_name در واقع به course_title وابسته است (هر درس، استاد مشخصی دارد)، نه مستقیم به کلید اصلی — این یعنی teacher_name باید داخل جدول courses باشد، نه در جدول ثبت‌نام‌ها.

نتیجه‌ی نهایی: همان سه جدولی که همیشه استفاده کردیم

وقتی این قوانین را روی جدول تخت بالا اعمال کنیم، دقیقاً به همان طراحی سه‌جدولی می‌رسیم که در فصل ۴ به بعد همیشه با آن کار کردیم:

sql
CREATE TABLE students (id INTEGER PRIMARY KEY, full_name TEXT);
CREATE TABLE courses (id INTEGER PRIMARY KEY, title TEXT, teacher_name TEXT);
CREATE TABLE enrollments (
  id INTEGER PRIMARY KEY,
  student_id INTEGER REFERENCES students(id),
  course_id INTEGER REFERENCES courses(id)
);

حالا نام استاد فقط یک‌بار، در جدول courses، ذخیره شده — تغییرش همیشه در یک‌جا اتفاق می‌افتد و هیچ‌وقت تناقضی پیش نمی‌آید.

نکته‌ی واقع‌بینانه: نرمال‌سازی کامل همیشه بهترین انتخاب نیست. در سیستم‌های گزارش‌گیری و تحلیل داده (Data Warehouse)، گاهی عمداً داده را کمی «دِنرمال» (Denormalized) نگه می‌دارند تا پرس‌وجوهای خواندن سریع‌تر اجرا شوند — چون آن سیستم‌ها بیشتر خوانده می‌شوند تا نوشته. انتخاب درست، همیشه به نیاز واقعی پروژه بستگی دارد.

جمع‌بندی این درس

  • داده‌ی تکراری در یک جدول تخت، باعث ناهنجاری‌های درج، به‌روزرسانی و حذف می‌شود.
  • 1NF: هر ستون فقط یک مقدار اتمی داشته باشد.
  • 2NF: هر ستون غیرکلیدی به کل کلید ترکیبی وابسته باشد، نه بخشی از آن.
  • 3NF: هیچ ستون غیرکلیدی نباید به ستون غیرکلیدی دیگری وابسته باشد.
  • نرمال‌سازی کامل، پیش‌فرض خوبی است؛ اما در سناریوهای خاص (مثل گزارش‌گیری)، دِنرمال‌سازی عمدی هم یک تصمیم مهندسی معتبر است.

در درس بعدی سراغ مدل‌های ER می‌رویم: چطور همین تصمیم‌های نرمال‌سازی را پیش از نوشتن حتی یک خط SQL، روی کاغذ طراحی کنیم.