ปัญหาการละเมิดข้อ จำกัด กุญแจต่างประเทศ


10

ฉันได้ระบุ 3 สถานการณ์

  1. นักเรียนที่ไม่มีการลงทะเบียน
  2. นักเรียนที่มีการลงทะเบียน แต่ไม่มีเกรด
  3. นักเรียนที่มีการลงทะเบียนและผลการเรียน

มีทริกเกอร์ในตารางการลงทะเบียนเพื่อคำนวณเกรดเฉลี่ย หากนักเรียนมีคะแนนจะทำการอัปเดตหรือแทรกรายการลงในตารางเกรดเฉลี่ย ไม่มีเกรดไม่มีรายการตารางเกรดเฉลี่ย

ฉันสามารถลบนักเรียนที่ไม่มีการลงทะเบียน (# 1) ฉันสามารถลบนักเรียนที่มีการลงทะเบียนและผลการเรียน (# 3 ด้านบน) แต่ฉันไม่สามารถลบนักเรียนที่มีการลงทะเบียน แต่ไม่มีคะแนน (# 2) ฉันได้รับการละเมิดข้อ จำกัด ในการอ้างอิง

คำสั่ง DELETE ขัดแย้งกับข้อ จำกัด การอ้างอิง "FK_dbo.GPA_dbo.Student_StudentID" ความขัดแย้งเกิดขึ้นในฐานข้อมูล "", ตาราง "dbo.GPA", คอลัมน์ 'StudentID'

หากฉันไม่สามารถลบนักเรียนใหม่ที่ไม่มีการลงทะเบียน (และไม่มีรายการ GPA) ฉันจะเข้าใจการละเมิดข้อ จำกัด แต่ฉันสามารถลบนักเรียนคนนั้นได้ เป็นนักเรียนที่มีการลงทะเบียนและไม่มีคะแนน (และยังไม่มีรายการเกรดเฉลี่ย) ที่ฉันไม่สามารถลบได้

ฉันได้ติดตั้งทริกเกอร์ของฉันเพื่อให้สามารถไปข้างหน้า ทีนี้ถ้าคุณมีการลงทะเบียนทริกเกอร์จะแทรกคุณลงในตารางเกรดเฉลี่ยไม่ว่าจะเกิดอะไรขึ้น แต่ฉันไม่เข้าใจปัญหาพื้นฐาน คำอธิบายใด ๆ ที่จะได้รับการชื่นชมมากที่สุด

สำหรับสิ่งที่คุ้มค่า:

  1. Visual Studio 2013 Professional
  2. IIS express (ภายในถึง VS2013)
  3. ASP.NET Web App ที่ใช้ EntityFramework 6.1.1
  4. องค์กร MS SQL Server 2014
  5. GPA.Value เป็นโมฆะ
  6. Enrollment.GradeID เป็นโมฆะ

นี่เป็นตัวอย่างของฐานข้อมูล:

ภาพฐานข้อมูล

- แก้ไข -

ตารางทั้งหมดถูกสร้างโดย 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ตรวจสอบเพื่อดูว่ามีรายการสำหรับนักศึกษา # 1 ในGPAตารางหรือไม่ ไม่มี (มิฉะนั้นการตรวจสอบ FK ในขั้นตอนที่ 3 จะทำให้เกิดข้อผิดพลาด)

ดังนั้นWHEN NOT MATCHEDประโยคในการMergeGPAพยายามINSERTแถวในGPAStudentID # 1 ความพยายามนี้ล้มเหลว (โดยมีข้อผิดพลาด FK) เนื่องจาก StudentID # 1 ถูกลบออกจากStudentตารางแล้ว (ที่ขั้นตอนที่ 1)


1
ฉันคิดว่าคุณกำลังจะทำอะไรบางอย่าง เมื่อนักเรียนถูกสร้างด้วยการลงทะเบียน แต่ไม่ได้รับการกำหนดคะแนนนักเรียนคนนั้นจะไม่มีรายการในตารางเกรดเฉลี่ย เมื่อฐานข้อมูลไปลบนักเรียนคนนั้นมันจะดูที่ฐานข้อมูลเห็นการลงทะเบียนเพื่อลบ แต่ไม่มีรายการเกรดเฉลี่ย ดังนั้นจึงตั้งค่าเกี่ยวกับการลบการลงทะเบียนซึ่งทำให้เกิดการเรียกซึ่งสร้างรายการ GPA ซึ่งทำให้เกิดการละเมิดข้อ จำกัด ? ดังนั้นทางออกคือการสร้างรายการเกรดเฉลี่ยเมื่อฉันสร้างนักเรียน จากนั้นแทรกทริกเกอร์ของฉันไม่จำเป็นต้องมีเงื่อนไขและขั้นตอนการจัดเก็บของฉันไม่จำเป็นต้องผสานเพียงแค่การอัปเดต
DowntownHippie

-1

โดยไม่ต้องอ่านทั้งหมดเพียงแค่จากแผนภาพ: คุณมีรายการในการลงทะเบียนหรือหนึ่งรายการในเกรดเฉลี่ยที่ชี้ไปที่นักเรียนที่คุณต้องการลบ

รายการที่มีคีย์ต่างประเทศจะต้องลบออกก่อน (หรือปุ่มตั้งค่าเป็นโมฆะ แต่เป็นการปฏิบัติที่ไม่เหมาะสม) ก่อนที่คุณจะสามารถลบรายการนักเรียนได้

นอกจากนี้ฐานข้อมูลบางตัวยังมี ON DELETE CASCADE ซึ่งจะลบรายการใด ๆ ที่มีคีย์ต่างประเทศไปยังฐานข้อมูลที่คุณต้องการลบ

อีกวิธีหนึ่งคือการไม่ประกาศว่าเป็นกุญแจต่างประเทศและใช้ค่าของคีย์เท่านั้น


ในกรณีที่มันล้มเหลวจะมีรายการในการลงทะเบียน แต่ไม่ใช่หนึ่งใน GPA
DowntownHippie

คุณมีข้อ จำกัด บางอย่างกับ ON DELETE CASCADE และบางข้อไม่มี ลองเพิ่มบรรทัดนั้นในข้อ จำกัด ทั้งหมด หลังจากนั้นจะลองปิดการใช้งานทริกเกอร์ทั้งหมดและหลังจากการทดสอบนั้นด้วยการตั้งค่าขั้นต่ำ ขอให้โชคดี
user44286

ฉันเห็นON DELETE CASCADEข้อความเหล่านั้น ไม่มีคำสั่งการสร้างตารางเหล่านี้หรือคำสั่งการลบที่เขียนด้วยมือพวกเขาทั้งหมดจะถูกสร้างขึ้นโดยกิจการ น้ำตกมีสาเหตุมาจากการลงทะเบียนมีกุญแจต่างประเทศที่ไม่ได้เป็นคีย์หลัก; ข้อ จำกัด กุญแจต่างประเทศของ GPA เป็นกุญแจสำคัญดังนั้นจึงไม่จำเป็นต้องมีการเรียงซ้อน ฉันได้ทำการทดสอบแล้วหากคุณลบนักเรียนที่มีรายการตารางเกรดเฉลี่ยรายการที่จะถูกลบ ปัญหาเดียวคือนักเรียนที่มีการลงทะเบียน แต่ไม่มีเกรดเฉลี่ย
DowntownHippie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.