外部キー制約違反の問題


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エクスプレス(VS2013の内部)。
  3. EntityFramework 6.1.1を使用するASP.NET Webアプリ。
  4. MS SQL Server 2014 Enterprise。
  5. GPA.Valueはnull可能です。
  6. Enrollment.GradeIDはnull可能です。

これはデータベースのスニペットです:

データベース画像

- 編集 -

テーブルはすべてEntityFrameworkによって作成され、SQL Server Management Studioを使用して作成しました。

次に、制約付きのテーブル作成ステートメントを示します。

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

これは、コントローラーの削除メソッドからのデバッグ出力です。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テーブルにStudent#1のエントリがあるかどうかを確認します。ない(そうしないと、ステップ3のFKチェックでエラーが発生します)。

したがって、のWHEN NOT MATCHED節は、StudentID#1の行をMergeGPA試みます。この試行は(FKエラーで)失敗します。StudentID#1がすでにテーブルから削除されているためです(ステップ1)。INSERTGPAStudent


1
あなたは何かに夢中になっていると思います。学生が登録で作成されたが、成績が割り当てられていない場合、その学生はGPAテーブルにエントリがありません。データベースがその生徒を削除しようとすると、データベースを見て、削除する登録を確認しますが、GPAエントリは確認しません。それで、登録の削除について設定します。これにより、GPAエントリを作成するトリガーが起動し、それにより制約違反が発生しますか?したがって、解決策は、学生を作成するときにGPAエントリを作成することです。そうすれば、挿入トリガーに条件を付ける必要がなくなり、ストアドプロシージャをマージする必要がなくなり、更新するだけで済みます。
DowntownHippie

-1

すべてを読み取らずに、図からのみ:登録のエントリまたはGPAのエントリのいずれかが、削除する生徒を指している。

Studentエントリを削除する前に、まず外部キーを含むエントリ(またはキーをnullに設定しますが、これはお勧めできません)を削除する必要があります。

また、一部のデータベースにはON DELETE CASCADEがあり、削除するエントリへの外部キーを持つエントリを削除します。

別の方法は、それらを外部キーとして宣言せず、キー値のみを使用することですが、これも推奨されません。


失敗した場合、登録にはエントリがありますが、GPAにはありません。
DowntownHippie

ON DELETE CASCADEを使用する場合と使用しない場合の制約があります。その行をすべての制約に追加してみてください。その後、すべてのトリガーを無効にしてから、最小限の設定でテストを行います。幸運を祈る
user44286 2014

私はそれらのON DELETE CASCADE声明を見ます。これらのテーブル作成ステートメントも削除ステートメントも手書きではなく、それらはすべてentityframeworkによって生成されます。カスケードは、登録に主キーではない外部キーがあるためです。GPAの外部キー制約は主キーであるため、カスケードは必要ありません。私はこれをテストしましたが、GPAテーブルエントリを持つ学生を削除すると、エントリが削除されます。唯一の問題は、登録はあるがgpaがない学生です。
DowntownHippie
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.