외래 키 제약 조건 위반 문제


10

3 가지 상황을 확인했습니다.

  1. 등록이없는 학생.
  2. 등록은했지만 성적은없는 학생.
  3. 등록 및 성적이있는 학생.

등록 테이블에는 GPA를 계산하는 트리거가 있습니다. 성적이있는 학생은 GPA 테이블에 항목을 업데이트하거나 삽입합니다. 성적, GPA 테이블 항목이 없습니다.

등록하지 않은 학생을 삭제할 수 있습니다 (# 1). 등록 및 성적이있는 학생을 삭제할 수 있습니다 (위의 3 번). 하지만 등록은했지만 성적 (# 2)은없는 학생을 삭제할 수 없습니다. 참조 제약 조건 위반이 발생합니다.

DELETE 문이 REFERENCE 제한 조건 "FK_dbo.GPA_dbo.Student_StudentID"와 충돌했습니다. 데이터베이스 "", 테이블 "dbo.GPA", 열 'StudentID'에서 충돌이 발생했습니다.

등록이없고 GPA 항목이없는 새 학생을 삭제할 수없는 경우 제약 조건 위반을 이해하지만 해당 학생을 삭제할 수 있습니다. 등록 할 수없고 성적이없고 (GPA 입학이없는) 학생은 삭제할 수 없습니다.

앞으로 나아갈 수 있도록 트리거를 패치했습니다. 이제 등록한 경우 트리거는 GPA 테이블에 무엇이든 삽입합니다. 그러나 나는 근본적인 문제를 이해하지 못한다. 모든 설명은 가장 감사하겠습니다.

가치있는 것 :

  1. Visual Studio 2013 Professional.
  2. IIS Express (VS2013 내부)
  3. EntityFramework를 사용하는 ASP.NET 웹앱 6.1.1.
  4. MS SQL Server 2014 엔터프라이즈.
  5. GPA.Value는 nullable입니다.
  6. Enrollment.GradeID는 널 입력 가능합니다.

다음은 데이터베이스 스 니펫입니다.

데이터베이스 이미지

- 편집하다 -

테이블은 모두 EntityFramework에 의해 생성되며 SQL Server Management Studio를 사용하여 생성했습니다.

제약 조건이있는 create table 문은 다음과 같습니다.

GPA 표:

CREATE TABLE [dbo].[GPA](
    [StudentID] [int] NOT NULL,
    [Value] [float] NULL,
  CONSTRAINT [PK_dbo.GPA] PRIMARY KEY CLUSTERED 
  (
    [StudentID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[GPA]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.GPA_dbo.Student_StudentID] 
  FOREIGN KEY([StudentID])
  REFERENCES [dbo].[Student] ([ID])

ALTER TABLE [dbo].[GPA] 
  CHECK CONSTRAINT [FK_dbo.GPA_dbo.Student_StudentID]

Enrollment 표:

CREATE TABLE [dbo].[Enrollment](
    [EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
    [CourseID] [int] NOT NULL,
    [StudentID] [int] NOT NULL,
    [GradeID] [int] NULL,
  CONSTRAINT [PK_dbo.Enrollment] PRIMARY KEY CLUSTERED 
  (
    [EnrollmentID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Course_CourseID] 
  FOREIGN KEY([CourseID])
  REFERENCES [dbo].[Course] ([CourseID])
  ON DELETE CASCADE

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Course_CourseID]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Grade_GradeID] 
  FOREIGN KEY([GradeID])
  REFERENCES [dbo].[Grade] ([GradeID])

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Grade_GradeID]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Student_StudentID] 
  FOREIGN KEY([StudentID])
  REFERENCES [dbo].[Student] ([ID])
  ON DELETE CASCADE

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Student_StudentID]

Student 표:

CREATE TABLE [dbo].[Student](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [EnrollmentDate] [datetime] NOT NULL,
    [LastName] [nvarchar](50) NOT NULL,
    [FirstName] [nvarchar](50) NOT NULL,
  CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED 
  (
    [ID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

트리거 는 다음과 같습니다 .

CREATE TRIGGER UpdateGPAFromUpdateDelete
ON Enrollment
AFTER UPDATE, DELETE AS
BEGIN
    DECLARE @UpdatedStudentID AS int
    SELECT @UpdatedStudentID = StudentID FROM DELETED
    EXEC MergeGPA @UpdatedStudentID
END

CREATE TRIGGER UpdateGPAFromInsert
ON Enrollment
AFTER INSERT AS
--DECLARE @InsertedGradeID AS int
--SELECT @InsertedGradeID = GradeID FROM INSERTED
--IF @InsertedGradeID IS NOT NULL
    BEGIN
        DECLARE @InsertedStudentID AS int
        SELECT @InsertedStudentID = StudentID FROM INSERTED
        EXEC MergeGPA @InsertedStudentID
    END

앞으로 나아갈 패치는 AFTER INSERT트리거 에서 해당 라인을 주석 처리하는 것이 었습니다 .

저장 프로시 저는 다음과 같습니다 .

CREATE PROCEDURE MergeGPA @StudentID int AS
MERGE GPA AS TARGET
USING (SELECT @StudentID) as SOURCE (StudentID)
ON (TARGET.StudentID = SOURCE.StudentID)
WHEN MATCHED THEN
    UPDATE
        SET Value = (SELECT Value FROM GetGPA(@StudentID))
WHEN NOT MATCHED THEN
INSERT (StudentID, Value)
    VALUES(SOURCE.StudentID, (SELECT Value FROM GetGPA(@StudentID)));

데이터베이스 기능 은 다음과 같습니다 .

CREATE FUNCTION GetGPA (@StudentID int) 
RETURNS TABLE
AS RETURN
SELECT ROUND(SUM (StudentTotal.TotalCredits) / SUM (StudentTotal.Credits), 2) Value
    FROM (
        SELECT 
            CAST(Credits as float) Credits
            , CAST(SUM(Value * Credits) as float) TotalCredits
        FROM 
            Enrollment e 
            JOIN Course c ON c.CourseID = e.CourseID
            JOIN Grade g  ON e.GradeID = g.GradeID
        WHERE
            e.StudentID = @StudentID AND
            e.GradeID IS NOT NULL
        GROUP BY
            StudentID
            , Value
            , e.courseID
            , Credits
    ) StudentTotal

다음은 컨트롤러의 delete 메소드의 디버그 출력입니다. select 문은 삭제할 항목을 쿼리하는 메소드입니다. 이 학생은 3 개의 등록이 있으며 REFERENCE, 3 번째 등록이 삭제 될 때 제약 문제가 발생합니다. 등록이 삭제되지 않았기 때문에 EF가 트랜잭션을 사용하고 있다고 가정합니다.

iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.ReaderExecuted;Timespan:00:00:00.0004945;Properties:
Command: SELECT 
    [Project2].[StudentID] AS [StudentID], 
    [Project2].[ID] AS [ID], 
    [Project2].[EnrollmentDate] AS [EnrollmentDate], 
    [Project2].[LastName] AS [LastName], 
    [Project2].[FirstName] AS [FirstName], 
    [Project2].[Value] AS [Value], 
    [Project2].[C1] AS [C1], 
    [Project2].[EnrollmentID] AS [EnrollmentID], 
    [Project2].[CourseID] AS [CourseID], 
    [Project2].[StudentID1] AS [StudentID1], 
    [Project2].[GradeID] AS [GradeID]
    FROM ( SELECT 
        [Limit1].[ID] AS [ID], 
        [Limit1].[EnrollmentDate] AS [EnrollmentDate], 
        [Limit1].[LastName] AS [LastName], 
        [Limit1].[FirstName] AS [FirstName], 
        [Limit1].[StudentID] AS [StudentID], 
        [Limit1].[Value] AS [Value], 
        [Extent3].[EnrollmentID] AS [EnrollmentID], 
        [Extent3].[CourseID] AS [CourseID], 
        [Extent3].[StudentID] AS [StudentID1], 
        [Extent3].[GradeID] AS [GradeID], 
        CASE WHEN ([Extent3].[EnrollmentID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM   (SELECT TOP (2) 
            [Extent1].[ID] AS [ID], 
            [Extent1].[EnrollmentDate] AS [EnrollmentDate], 
            [Extent1].[LastName] AS [LastName], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent2].[StudentID] AS [StudentID], 
            [Extent2].[Value] AS [Value]
            FROM  [dbo].[Student] AS [Extent1]
            LEFT OUTER JOIN [dbo].[GPA] AS [Extent2] ON [Extent1].[ID] = [Extent2].[StudentID]
            WHERE [Extent1].[ID] = @p__linq__0 ) AS [Limit1]
        LEFT OUTER JOIN [dbo].[Enrollment] AS [Extent3] ON [Limit1].[ID] = [Extent3].[StudentID]
    )  AS [Project2]
    ORDER BY [Project2].[StudentID] ASC, [Project2].[ID] ASC, [Project2].[C1] ASC: 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0012696;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0002634;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0002512;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Error: 0 : Error executing command: DELETE [dbo].[Student]
WHERE ([ID] = @0) Exception: System.Data.SqlClient.SqlException (0x80131904): The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.GPA_dbo.Student_StudentID". The conflict occurred in database "<databasename>", table "dbo.GPA", column 'StudentID'.
The statement has been terminated.

답변:


7

타이밍 문제입니다. StudentID # 1 삭제를 고려하십시오.

  1. Student테이블 에서 행이 삭제됩니다.
  2. 계단식 삭제는 해당 행을 제거합니다. Enrollment
  3. 외래 키 관계 GPA-> Student확인
  4. 방아쇠가 터져 MergeGPA

이 시점 MergeGPA에서 GPA테이블 에 학생 # 1에 대한 항목이 있는지 확인 합니다. 존재하지 않습니다 (그렇지 않으면 3 단계의 FK 검사에서 오류가 발생했을 수 있습니다).

따라서이 WHEN NOT MATCHED절은 StudentID # 1 에 대한 행을 MergeGPA시도합니다 . StudentID # 1이 이미 테이블 에서 삭제 되었기 때문에 (FK 오류와 함께)이 시도는 실패합니다 (1 단계).INSERTGPAStudent


1
나는 당신이 무언가에 있다고 생각합니다. 학생이 등록 상태로 작성되었지만 등급이 지정되지 않은 경우 해당 학생은 GPA 테이블에 항목이 없습니다. 데이터베이스가 해당 학생을 삭제하려고하면 데이터베이스를보고 GPA 항목이없는 삭제를위한 등록을 확인합니다. 따라서 등록 삭제에 대해 설정하여 트리거가 발생하여 GPA 항목이 생성되어 제약 조건 위반이 발생합니까? 따라서 해결책은 학생을 만들 때 GPA 항목을 만드는 것입니다. 그런 다음 삽입 트리거에는 조건이 필요하지 않으며 저장 프로 시저는 병합 일 필요가 없으며 업데이트 일뿐입니다.
DowntownHippie

-1

다이어그램에서 전부 읽지 않고 : 등록 항목이나 GPA에 항목을 삭제하려는 학생을 가리 킵니다.

학생 항목을 삭제하려면 외래 키가있는 항목을 먼저 삭제해야합니다 (또는 키가 null로 설정되어 있지만 이는 잘못된 방법입니다).

또한 일부 데이터베이스에는 ON DELETE CASCADE가 있으며, 삭제하려는 외래 키가있는 항목을 모두 삭제합니다.

다른 방법은 외래 키로 선언하지 않고 키 값만 사용하는 것이지만 권장하지는 않습니다.


실패하는 경우 등록 항목은 있지만 GPA 항목은 없습니다.
DowntownHippie

ON DELETE CASCADE에는 제한이 있고 일부는 없습니다. 해당 줄을 모든 제약 조건에 추가하십시오. 그 후 모든 트리거를 비활성화하고 최소한의 설정으로 테스트 후에 시도하십시오. 행운을 빌어 요
user44286

나는 그 ON DELETE CASCADE진술을 본다 . 이러한 테이블 생성 문이나 delete 문은 직접 작성되지 않으며 모두 엔티티 프레임 워크에 의해 생성됩니다. 캐스케이드는 등록에 기본 키가 아닌 외래 키가 있기 때문입니다. GPA의 외래 키 제약 조건은 기본 키이므로 캐스케이드가 필요하지 않습니다. GPA 테이블 항목이있는 학생을 삭제하면 항목이 삭제됩니다. 유일한 문제는 등록은되었지만 gpa는없는 학생입니다.
DowntownHippie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.