خانه/ وبلاگ/ خطای conflicted with the FOREIGN KEY constraint
خطاهای رایج SQL

رفع خطای The DELETE / INSERT statement conflicted with the FOREIGN KEY constraint

خطاهای رایج SQL ۵ آبان ۱۴۰۵ 9 دقیقه مطالعه

یکی از خطاهای رایج هنگام کار با دو جدول مرتبط این است:

text
Msg 547, Level 16, State 0
The DELETE statement conflicted with the REFERENCE constraint "FK_Enrollment_Student".
The conflict occurred in database "SchoolDB", table "dbo.Enrollment", column 'StudentId'.

و نسخه‌ی دیگر آن هنگام درج:

text
Msg 547, Level 16, State 0
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Enrollment_Course".
The conflict occurred in database "SchoolDB", table "dbo.Course", column 'CourseId'.
خلاصه: کلید خارجی به ما اجازه نمی‌دهد «ردیف یتیم» بسازیم. در DELETE یعنی «این ردیف هنوز فرزند دارد»؛ در INSERT یعنی «والدی که به آن اشاره می‌کنی وجود ندارد».

متن خطا را درست بخوان

بخش پیامیعنی
The DELETE statement conflicted with the REFERENCE constraintمی‌خواهی ردیف والد را حذف کنی ولی فرزند دارد
The INSERT statement conflicted with the FOREIGN KEY constraintمی‌خواهی فرزند را درج کنی ولی والد ندارد
FK_Enrollment_Studentنام قید
table ... column ...جدول و ستونی که در آن تعارض پیدا شده (در DELETE: جدول فرزند، در INSERT: جدول والد)

حالت ۱: خطا هنگام DELETE

sql
DELETE FROM Student WHERE StudentId = 5;   -- خطا: Enrollment فرزند دارد

اول ببین چه ردیف‌هایی به آن وابسته‌اند:

sql
SELECT * FROM Enrollment WHERE StudentId = 5;

بسته به منطق کسب‌وکار یکی از راه‌حل‌های زیر را انتخاب کن:

راه‌حل الف) ابتدا فرزندها را حذف کن

sql
BEGIN TRANSACTION;
  DELETE FROM Enrollment WHERE StudentId = 5;
  DELETE FROM Student    WHERE StudentId = 5;
COMMIT;

راه‌حل ب) حذف آبشاری (ON DELETE CASCADE)

اگر ثبت‌نام بدون دانشجو بی‌معنی است، قید را با CASCADE بازتعریف کن تا فرزندها خودکار حذف شوند:

sql
ALTER TABLE Enrollment DROP CONSTRAINT FK_Enrollment_Student;

ALTER TABLE Enrollment
ADD CONSTRAINT FK_Enrollment_Student FOREIGN KEY (StudentId)
    REFERENCES Student(StudentId) ON DELETE CASCADE;

راه‌حل ج) حذف منطقی

برای داده‌های مهم، ردیف را واقعاً پاک نکن؛ آن را غیرفعال علامت بزن:

sql
ALTER TABLE Student ADD IsDeleted BIT NOT NULL DEFAULT 0;
UPDATE Student SET IsDeleted = 1 WHERE StudentId = 5;

حالت ۲: خطا هنگام INSERT یا UPDATE

sql
INSERT INTO Enrollment (StudentId, CourseId) VALUES (1, 99);   -- CourseId=99 وجود ندارد

بررسی کن مقدار کلید خارجی در جدول والد هست یا نه:

sql
SELECT * FROM Course WHERE CourseId = 99;      -- خالی؟ پس درج مجاز نیست

راه‌حل: یا ابتدا والد را درج کن، یا مقدار درست را در ستون بگذار. اگر ستون اختیاری است می‌توان NULL گذاشت.

پیدا کردن همه‌ی جدول‌هایی که به یک جدول وابسته‌اند

sql
SELECT fk.name AS ForeignKey,
       OBJECT_NAME(fk.parent_object_id)     AS ChildTable,
       OBJECT_NAME(fk.referenced_object_id) AS ParentTable,
       fk.delete_referential_action_desc    AS OnDelete
FROM sys.foreign_keys fk
WHERE fk.referenced_object_id = OBJECT_ID('dbo.Student');

پیدا کردن ردیف‌های یتیم موجود

اگر قید را بعداً اضافه می‌کنی و خطا می‌گیری، احتمالاً داده‌ی نامعتبر داری. این کوئری فرزندان بدون والد را پیدا می‌کند:

sql
SELECT e.*
FROM Enrollment e
LEFT JOIN Student s ON s.StudentId = e.StudentId
WHERE s.StudentId IS NULL;

غیرفعال کردن موقت قید (با احتیاط)

sql
ALTER TABLE Enrollment NOCHECK CONSTRAINT FK_Enrollment_Student;
-- ... بارگذاری داده ...
ALTER TABLE Enrollment WITH CHECK CHECK CONSTRAINT FK_Enrollment_Student;  -- بررسی مجدد
غیرفعال کردن قید فقط باید برای بارگذاری‌های کنترل‌شده باشد. فراموش کردن بررسی مجدد، داده‌ی ناسازگار در پایگاه‌داده باقی می‌گذارد.

چگونه از این خطا جلوگیری کنیم؟

  • از ابتدا ترتیب درست را رعایت کن: اول والد، بعد فرزند؛ برای حذف برعکس.
  • برای عملیات چندجدولی از تراکنش استفاده کن.
  • برای حذف‌های پرتکرار، ON DELETE CASCADE یا حذف منطقی را در طراحی در نظر بگیر.
  • نام قیدها را معنادار انتخاب کن تا پیام خطا سریع قابل‌فهم باشد.

برای شناخت بهتر کلیدها، مقاله‌ی تفاوت Primary Key، Unique و Foreign Key و درس Foreign Key را بخوان.

سوالات متداول درباره‌ی این موضوع

Msg 547 دقیقاً یعنی چه؟

یعنی عملیات (INSERT، UPDATE یا DELETE) قید کلید خارجی را نقض می‌کند: یا ردیفی را درج می‌کنی که والدش وجود ندارد، یا والدی را حذف می‌کنی که فرزند دارد.

آیا می‌توانم قید را موقتاً غیرفعال کنم؟

فنی بله (NOCHECK CONSTRAINT)، اما توصیه نمی‌شود چون ردیف‌های نامعتبر وارد می‌شوند و بعداً مشکل‌ساز می‌شوند. فقط برای بارگذاری‌های کنترل‌شده و با بررسی مجدد (WITH CHECK CHECK) استفاده کن.

ON DELETE CASCADE همیشه راه‌حل خوبی است؟

نه. CASCADE ممکن است ناخواسته داده‌های زیادی را حذف کند. برای داده‌های مهم (مثل سفارش‌های مشتری) بهتر است حذف منطقی (IsDeleted) داشته باشی.

مصطفی موسویسازنده و نویسنده‌ی SQLFarsi. اگر اشتباهی در مقاله دیدی یا پیشنهادی داری، از طریق mousavi.dev خبر بده.

می‌خوای SQL را از پایه و با تمرین یاد بگیری؟

دوره‌ی رایگان SQLFarsi را با Playground و پروژه‌های واقعی دنبال کن.

شروع یادگیری