ข้อ จำกัด คีย์ต่างประเทศอาจทำให้เกิดรอบหรือเส้นทางเรียงซ้อนหลายเส้นทาง?


176

ฉันมีปัญหาเมื่อฉันพยายามเพิ่มข้อ จำกัด ในตารางของฉัน ฉันได้รับข้อผิดพลาด:

แนะนำข้อ จำกัด ที่สำคัญของต่างประเทศ 'FK74988DB24B3C886' ในตาราง 'พนักงาน' อาจทำให้เกิดรอบหรือเส้นทางเรียงซ้อนหลายเส้นทาง ระบุ ON DELETE NO ACTION หรือ UPDATE NO ACTION หรือแก้ไขข้อ จำกัด ของรหัสต่างประเทศ

ข้อ จำกัด ของฉันอยู่ระหว่างCodeตารางและemployeeตาราง CodeตารางมีId, Name, FriendlyName, และType Valueกระบวนการemployeeมีจำนวนฟิลด์ที่อ้างอิงรหัสเพื่อให้สามารถมีการอ้างอิงสำหรับรหัสแต่ละประเภทได้

ฉันต้องการให้ฟิลด์ถูกตั้งค่าเป็นโมฆะหากรหัสที่อ้างอิงถูกลบ

ความคิดใดที่ฉันสามารถทำได้


หนึ่งในวิธีแก้ปัญหาอยู่ที่นี่
IsmailS

คำตอบ:


180

SQL Server ทำการนับเส้นทาง cascade อย่างง่าย ๆ และแทนที่จะพยายามหาว่ามีวัฏจักรใดเกิดขึ้นจริง ๆ จะถือว่าเลวร้ายที่สุดและปฏิเสธที่จะสร้างการดำเนินการอ้างอิง (CASCADE): คุณสามารถและควรสร้างข้อ จำกัด โดยไม่มีการดำเนินการอ้างอิง หากคุณไม่สามารถเปลี่ยนแปลงการออกแบบของคุณ (หรือทำเช่นนั้นจะทำให้สิ่งต่าง ๆ ) คุณควรพิจารณาใช้ทริกเกอร์เป็นทางเลือกสุดท้าย

FWIW การแก้ไขเส้นทางน้ำตกเป็นปัญหาที่ซับซ้อน ผลิตภัณฑ์ SQL อื่น ๆ จะมองข้ามปัญหาและอนุญาตให้คุณสร้างรอบซึ่งในกรณีนี้จะเป็นการแข่งขันเพื่อดูว่าจะเขียนทับค่าล่าสุดอาจเป็นความไม่รู้ของนักออกแบบ (เช่น ACE / Jet ทำสิ่งนี้) ฉันเข้าใจว่าผลิตภัณฑ์ SQL บางอย่างจะพยายามแก้ไขกรณีง่าย ๆ ข้อเท็จจริงยังคงอยู่ SQL Server ไม่ได้ลองเล่นปลอดภัยเป็นพิเศษโดยไม่อนุญาตให้มีมากกว่าหนึ่งพา ธ และอย่างน้อยก็บอกคุณเช่นนั้น

Microsoft แนะนำให้ใช้ทริกเกอร์แทนข้อ จำกัด FK


2
สิ่งหนึ่งที่ฉันยังไม่เข้าใจคือถ้า "ปัญหา" นี้สามารถแก้ไขได้โดยการใช้ทริกเกอร์แล้วทำไมทริกเกอร์จะไม่ "ทำให้เกิดวงจรหรือหลายเส้นทางน้ำตก ... "?
armen

5
@ armen: เนื่องจากทริกเกอร์ของคุณจะให้เหตุผลอย่างชัดเจนว่าระบบไม่สามารถระบุตัวตนของมันเองได้เช่นหากมีหลายเส้นทางสำหรับการดำเนินการอ้างอิงแบบลบแล้วโค้ดทริกเกอร์ของคุณจะกำหนดว่าตารางใดจะถูกลบและอยู่ในลำดับใด
oneday เมื่อ

6
และทริกเกอร์จะเรียกใช้งานหลังจากการดำเนินการครั้งแรกเสร็จสิ้นดังนั้นจึงไม่มีการแข่งขันเกิดขึ้น
Bon

2
@dumbledad: ฉันหมายถึงใช้ทริกเกอร์เฉพาะเมื่อมีข้อ จำกัด (อาจเป็นการรวมกัน) ไม่สามารถทำงานให้เสร็จได้ ข้อ จำกัด เป็นสิ่งที่เปิดเผยและการนำไปใช้นั้นเป็นความรับผิดชอบของระบบ ทริกเกอร์เป็นรหัสขั้นตอนและคุณต้องโค้ด (และดีบัก) การใช้งานและทนต่อข้อเสียของมัน (ประสิทธิภาพแย่ลง ฯลฯ )
oneday เมื่อ

1
ปัญหาเกี่ยวกับสิ่งนี้คือทริกเกอร์ทำงานได้ตราบใดที่คุณลบข้อ จำกัด foreign key ซึ่งหมายความว่าคุณไม่มีการตรวจสอบความสมบูรณ์ของ Referential บนแทรกฐานข้อมูลดังนั้นคุณจึงต้องใช้ทริกเกอร์เพิ่มเติมเพื่อจัดการ โซลูชันทริกเกอร์เป็นรูกระต่ายที่นำไปสู่การออกแบบฐานข้อมูลที่เสื่อมโทรม
ตริโน

99

สถานการณ์ทั่วไปที่มีหลายเส้นทางจะเป็นเช่นนี้: ตารางต้นแบบที่มีรายละเอียดสองรายการสมมติว่า "Master" และ "Detail1" และ "Detail2" รายละเอียดทั้งสองถูกลบทั้งหมด จนถึงไม่มีปัญหา แต่จะเกิดอะไรขึ้นถ้ารายละเอียดทั้งสองมีความสัมพันธ์แบบหนึ่งต่อหลายคนกับตารางอื่น ๆ (พูดว่า "SomeOtherTable") SomeOtherTable มี Detail1ID-column และ Detail2ID-column

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

กล่าวอีกนัยหนึ่ง: บางระเบียนใน SomeOtherTable เชื่อมโยงกับ Detail1-records และบางระเบียนใน SomeOtherTable เชื่อมโยงกับ Detail2 records แม้ว่าจะรับประกันได้ว่า SomeOtherTable-records ไม่เคยอยู่ในรายละเอียดทั้งคู่ แต่ตอนนี้มันเป็นไปไม่ได้ที่จะทำบันทึกของ SomeOhterTable เพื่อลบรายละเอียดทั้งสองเนื่องจากมีเส้นทางแบบเรียงซ้อนจาก Master ไปยัง SomeOtherTable (หนึ่งผ่าน Detail1 และ One ผ่าน Detail2) ตอนนี้คุณอาจเข้าใจสิ่งนี้แล้ว นี่คือทางออกที่เป็นไปได้:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

ฟิลด์ ID ทั้งหมดคือคีย์ฟิลด์และการเพิ่มอัตโนมัติ crux อยู่ในเขตข้อมูล DetailMainId ของตารางรายละเอียด ฟิลด์เหล่านี้มีทั้งข้อ จำกัด เรื่องกุญแจและตัวอ้างอิง ตอนนี้คุณสามารถเรียงซ้อนการลบทุกอย่างได้โดยการลบข้อมูลหลักเท่านั้น ข้อเสียคือสำหรับแต่ละ detail1-record และสำหรับแต่ละ detail2 นั้นจะต้องมี DetailMain-record (ซึ่งถูกสร้างขึ้นจริงก่อนเพื่อให้ได้ ID ที่ถูกต้องและไม่ซ้ำกัน)


1
ความคิดเห็นของคุณช่วยให้ฉันเข้าใจปัญหาที่ฉันพบเจอเป็นอย่างมาก ขอบคุณ! ฉันต้องการปิดการลบแบบเรียงซ้อนสำหรับเส้นทางใดเส้นทางหนึ่งจากนั้นจัดการการลบระเบียนอื่น ๆ ด้วยวิธีอื่น ๆ (กระบวนงานที่เก็บไว้ทริกเกอร์โดยรหัส ฯลฯ ) แต่ฉันจะแก้ปัญหาของคุณ (การจัดกลุ่มในเส้นทางเดียว) ในใจสำหรับการใช้งานที่แตกต่างกันที่เป็นไปได้ของปัญหาเดียวกัน ...
อิสระ

1
One up สำหรับการใช้คำว่า crux (และสำหรับการอธิบาย)
masterwok

สิ่งนี้ดีกว่าการเขียนทริกเกอร์หรือไม่ ดูเหมือนว่าแปลกที่จะเพิ่มตารางเพิ่มเติมเพื่อให้น้ำตกทำงานได้
dumbledad

อะไรที่ดีกว่าการเขียนทริกเกอร์ เหตุผลของพวกเขาคือทึบแสงและพวกเขาไม่มีประสิทธิภาพเมื่อเทียบกับสิ่งอื่น การแบ่งตารางขนาดใหญ่ออกเป็นตารางย่อยเพื่อการควบคุมที่ละเอียดยิ่งขึ้นเป็นเพียงผลลัพธ์ตามธรรมชาติของฐานข้อมูลที่ได้รับการปรับให้เป็นมาตรฐานที่ดีขึ้น
ตริโน

12

ฉันจะชี้ให้เห็นว่า (ตามหน้าที่) มีความแตกต่างใหญ่ระหว่างรอบและ / หรือหลายเส้นทางใน SCHEMA และ DATA ในขณะที่รอบและบางที multipaths ในข้อมูลสามารถประมวลผลที่ซับซ้อนอย่างแน่นอนและทำให้เกิดปัญหาประสิทธิภาพ (ค่าใช้จ่ายของการจัดการ "ถูกต้อง") ค่าใช้จ่ายของลักษณะเหล่านี้ในสคีมาควรอยู่ใกล้กับศูนย์

เนื่องจากวัฏจักรที่ชัดเจนที่สุดใน RDBs เกิดขึ้นในโครงสร้างแบบลำดับชั้น (org org, part, subpart, ฯลฯ ) โชคไม่ดีที่ SQL Server ถือว่าเลวร้ายที่สุด เช่น schema cycle == data cycle ที่จริงแล้วถ้าคุณใช้ข้อ จำกัด ของ RI คุณจะไม่สามารถสร้างวัฏจักรในข้อมูลได้

ฉันสงสัยว่าปัญหา multipath จะคล้ายกัน; เช่นหลายเส้นทางในสคีมาไม่จำเป็นต้องบ่งบอกถึงเส้นทางหลายเส้นทางในข้อมูล แต่ฉันมีประสบการณ์น้อยลงกับปัญหาหลายเส้นทาง

แน่นอนถ้า SQL Server ทำอนุญาตให้รอบมันยังคงเป็นเรื่องที่ความลึก 32 แต่ที่น่าจะเพียงพอสำหรับกรณีส่วนใหญ่ (แย่มากที่ไม่ใช่การตั้งค่าฐานข้อมูล!)

ทริกเกอร์ "แทนที่จะลบ" ไม่ทำงานเช่นกัน ครั้งที่สองที่มีการเยี่ยมชมตารางทริกเกอร์จะถูกละเว้น ดังนั้นถ้าคุณต้องการจำลองน้ำตกจริง ๆ คุณจะต้องใช้กระบวนงานที่เก็บไว้ในที่ที่มีวงจร อย่างไรก็ตามการลบ - ทริกเกอร์จะทำงานได้ในหลายกรณีอย่างไรก็ตาม

Celko แนะนำวิธีที่ "ดีกว่า" ในการเป็นตัวแทนลำดับชั้นที่ไม่แนะนำรอบ แต่มีการแลกเปลี่ยน


"ถ้าคุณกำลังใช้ข้อ จำกัด ของ RI คุณจะไม่สามารถสร้างวัฏจักรในข้อมูลได้!" -- จุดดี!
oneday เมื่อ

แน่นอนว่าคุณสามารถสร้างข้อมูลเวียนได้ แต่ด้วย MSSQL เพียงใช้ UPDATE RDBM อื่นสนับสนุนข้อ จำกัด รอการตัดบัญชี (ทำให้มั่นใจในความสมบูรณ์ในเวลาที่กระทำไม่ใช่เวลาของการแทรก / อัพเดต / ลบ)
Carl Krig

7

มีบทความที่อธิบายวิธีการดำเนินการลบหลายเส้นทางโดยใช้ทริกเกอร์ บางทีนี่อาจเป็นประโยชน์สำหรับสถานการณ์ที่ซับซ้อน

http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/


3

ด้วยเสียงของมันคุณมีแอคชั่น OnDelete / OnUpdate บนหนึ่งใน Foreign Keys ที่มีอยู่ซึ่งจะแก้ไขตารางรหัสของคุณ

ดังนั้นโดยการสร้าง Foreign Key คุณจะต้องสร้างปัญหาแบบวนขึ้น

เช่นการอัปเดตพนักงานทำให้รหัสเปลี่ยนแปลงโดย On Update Action ทำให้พนักงานเปลี่ยนแปลงโดย On Update Action ... ฯลฯ ...

หากคุณโพสต์คำจำกัดความตารางของคุณสำหรับทั้งสองตาราง & คำจำกัดความของคีย์ / ข้อ จำกัด ต่างประเทศของคุณเราควรจะสามารถบอกคุณได้ว่าปัญหาอยู่ที่ใด ...


1
พวกเขาค่อนข้างยาวดังนั้นฉันไม่คิดว่าฉันสามารถโพสต์ไว้ที่นี่ แต่ฉันจะขอบคุณความช่วยเหลือของคุณมาก - ไม่รู้ว่ามีวิธีใดบ้างที่ฉันสามารถส่งพวกเขามาให้คุณ? ลองและอธิบายมัน: ข้อ จำกัด เพียงอย่างเดียวที่มีอยู่คือจาก 3 ตารางที่ทุกคนมีเขตข้อมูลที่อ้างถึงรหัสด้วยรหัส INT แบบง่าย ปัญหาดูเหมือนว่าพนักงานมีหลายสาขาที่อ้างอิงตารางรหัสและฉันต้องการให้พวกเขาทั้งหมดเพื่อเรียงซ้อน SET NULL ทั้งหมดที่ฉันต้องการคือเมื่อรหัสถูกลบการอ้างอิงถึงพวกเขาควรจะตั้งค่าเป็นโมฆะทุกที่

โพสต์พวกเขาต่อไป ... ฉันไม่คิดว่าทุกคนที่นี่จะทราบและหน้าต่างรหัสจะจัดรูปแบบได้อย่างถูกต้องในบล็อกเลื่อน :)
เอียนแคมป์เบล

2

ทั้งนี้เนื่องจาก Emplyee อาจมีการรวบรวมของนิติบุคคลอื่น ๆ ว่าคุณสมบัติและคุณสมบัติอาจมีมหาวิทยาลัยอื่น ๆ เช่น

public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}

}

public class Qualification{

public Employee Employee {get;set;}

public virtual ICollection<University> Universities {get;set;}

}

public class University{

public Qualification Qualification {get;set;}

}

ใน DataContext อาจเป็นดังนี้

protected override void OnModelCreating(DbModelBuilder modelBuilder){

modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);

}

ในกรณีนี้จะมีสายงานจากพนักงานถึงคุณสมบัติและจากคุณสมบัติสู่มหาวิทยาลัย ดังนั้นฉันจึงโยนข้อยกเว้นเดียวกันกับฉัน

มันทำงานสำหรับฉันเมื่อฉันเปลี่ยน

    modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); 

ถึง

    modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);

1

ทริกเกอร์เป็นวิธีแก้ปัญหาสำหรับปัญหานี้:

IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
    drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
    drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
    DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go  
    create table fktest2 (id1 int, id2 int, anQId int identity,
        FOREIGN KEY (id1) REFERENCES fktest1 (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE/*,    
        FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
            ON DELETE CASCADE
            ON UPDATE CASCADE*/ 
            )
go

CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
    if @@ROWCOUNT = 0
        return
    set nocount on

    -- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
    -- Compiler complains only when you use multiple cascased. It throws this compile error:
    -- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, 
    -- or modify other FOREIGN KEY constraints.
    IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
    begin       
        update fktest2 set id2 = i.id
            from deleted d
            join fktest2 on d.id = fktest2.id2
            join inserted i on i.anqid = d.anqid        
    end         
    if exists (select 1 from deleted)       
        DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO

insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)

insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)

select * from fktest1
select * from fktest2

update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22

select * from fktest1
select * from fktest2

0

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

protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
    modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}

หรือปิดคุณสมบัตินี้อย่างสมบูรณ์:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

-2

วิธีแก้ไขปัญหาของฉันที่พบโดยใช้ ASP.NET Core 2.0 และ EF Core 2.0 คือการดำเนินการต่อไปนี้ตามลำดับ:

  1. เรียกใช้update-databaseคำสั่งในแพคเกจ Management Management Console (PMC) เพื่อสร้างฐานข้อมูล (ผลลัพธ์นี้ใน "ข้อ จำกัด ของการแนะนำต่างประเทศคีย์ ... อาจทำให้เกิดรอบหรือหลายเส้นทางข้อผิดพลาด")

  2. เรียกใช้script-migration -Idempotentคำสั่งใน PMC เพื่อสร้างสคริปต์ที่สามารถเรียกใช้โดยไม่คำนึงถึงตาราง / ข้อ จำกัด ที่มีอยู่

  3. ใช้สคริปต์ผลลัพธ์และค้นหาON DELETE CASCADEและแทนที่ด้วยON DELETE NO ACTION

  4. ดำเนินการ SQL ที่ถูกแก้ไขกับฐานข้อมูล

ตอนนี้การย้ายข้อมูลของคุณควรเป็นข้อมูลล่าสุดและการลบแบบเรียงซ้อนไม่ควรเกิดขึ้น

น่าเสียดายที่ฉันไม่สามารถหาวิธีการนี้ได้ใน Entity Framework Core 2.0

โชคดี!


คุณสามารถเปลี่ยนไฟล์การย้ายข้อมูลของคุณให้เป็นแบบนั้นได้ (โดยไม่ต้องเปลี่ยนสคริปต์ sql) เช่นในไฟล์การโยกย้ายของคุณคุณสามารถตั้งค่าการดำเนินการลบเพื่อ จำกัด จาก Cascade
Rushi Soni

เป็นการดีกว่าที่จะระบุสิ่งนี้โดยใช้คำอธิบายประกอบที่คล่องแคล่วเพื่อที่คุณจะได้ไม่ต้องจำสิ่งนี้หากคุณลบและสร้างโฟลเดอร์การย้ายข้อมูลขึ้นใหม่
Allen Wang

จากประสบการณ์ของฉันคำอธิบายประกอบที่คล่องแคล่วสามารถใช้ได้และควรใช้ (ฉันใช้พวกเขา) แต่บ่อยครั้งที่พวกเขาค่อนข้างบั๊ก เพียงแค่ระบุในรหัสไม่ได้ผลเสมอไป
user1477388
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.