[sql] 외래 키 제약 조건으로 인해 사이클이나 다중 캐스케이드 경로가 발생할 수 있습니까?

테이블에 제약 조건을 추가하려고 할 때 문제가 있습니다. 오류가 발생합니다.

테이블 ‘직원’에 FOREIGN KEY 제약 조건 ‘FK74988DB24B3C886’을 도입하면 사이클 또는 여러 계단식 경로가 발생할 수 있습니다. ON DELETE NO ACTION 또는 ON UPDATE NO ACTION을 지정하거나 다른 FOREIGN KEY 제약 조건을 수정하십시오.

내 제약 조건은 Code테이블과 테이블 사이에 employee있습니다. Code테이블에 포함 된 Id, Name, FriendlyName, TypeValue. 는 employee기준 코드 때문에 코드의 각 유형에 대한 참조가있을 수 있다는 것이 다수의 필드를 갖는다.

참조 된 코드가 삭제되면 필드를 null로 설정해야합니다.

내가 어떻게 할 수있는 아이디어가 있습니까?



답변

SQL Server는 캐스케이드 경로의 간단한 계산을 수행하며 실제로주기가 존재하는지 여부를 해결하려고 시도하는 대신 최악의 상황을 가정하고 참조 조치 (CASCADE)를 작성하지 않습니다. 참조 조치 없이도 제한 조건을 작성할 수 있으며 여전히 작성해야합니다. 디자인을 변경할 수 없거나 (그렇게하면 문제가 발생할 수있는 경우) 최후의 수단으로 트리거 사용을 고려해야합니다.

캐스케이드 경로를 해결하는 FWIW는 복잡한 문제입니다. 다른 SQL 제품은 단순히 문제를 무시하고주기를 만들 수 있도록합니다.이 경우 어떤 값을 마지막으로 덮어 쓰는지, 아마도 디자이너의 무지 (예 : ACE / Jet에서 수행)를 보는 경쟁이 될 것입니다. 일부 SQL 제품이 간단한 사례를 해결하려고 시도한다는 것을 알고 있습니다. 사실, SQL Server는 시도하지도 않고 하나 이상의 경로를 허용하지 않아 매우 안전하며 최소한 그렇게 말합니다.

마이크로 소프트 자체는 조언 대신 FK 제약의 트리거의 사용을.


답변

여러 개의 캐스 케이 딩 경로가있는 일반적인 상황은 다음과 같습니다. 두 개의 세부 사항이있는 마스터 테이블 “Master”및 “Detail1″및 “Detail2″라고합시다. 두 세부 사항 모두 계단식 삭제입니다. 지금까지 아무런 문제가 없습니다. 그러나 두 세부 사항에 다른 테이블과 일대 다 관계가있는 경우 (예 : “SomeOtherTable”) SomeOtherTable에는 Detail1ID 열과 Detail2ID 열이 있습니다.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

즉, SomeOtherTable의 일부 레코드는 Detail1 레코드와 연결되고 SomeOtherTable의 일부 레코드는 Detail2 레코드와 연결됩니다. SomeOtherTable 레코드가 두 Details에 모두 속하지 않는다고 보장 되더라도 Master에서 SomeOtherTable까지의 다중 계단식 경로 (Detail1을 통해 하나와 Detail2를 통해 하나씩)가 있기 때문에 SomeOhterTable의 레코드를 두 가지 세부 정보 모두에 대해 계단식으로 삭제할 수 없습니다. 이제 당신은 이미 이것을 이해했을 것입니다. 가능한 해결책은 다음과 같습니다.

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

모든 ID 필드는 키 필드 및 자동 증분입니다. 요점은 세부 사항 테이블의 DetailMainId 필드에 있습니다. 이 필드는 핵심 및 참조 제약 조건입니다. 이제 마스터 레코드 만 삭제하여 모든 항목을 계단식으로 삭제할 수 있습니다. 단점은 각 detail1 레코드와 detail2 레코드마다 DetailMain 레코드 (정확하고 고유 한 ID를 얻기 위해 먼저 작성 됨)도 있어야한다는 것입니다.


답변

나는 (기능적으로) SCHEMA와 DATA의 사이클 및 / 또는 다중 경로 사이에 큰 차이가 있음을 지적합니다. DATA의주기 및 다중 경로는 처리를 복잡하게하고 성능 문제 ( “적절한”처리 비용)를 유발할 수 있지만 스키마에서 이러한 특성의 비용은 0에 가까워 야합니다.

RDB에서 가장 명백한주기는 계층 구조 (org 차트, 부분, 하위 부분 등)에서 발생하기 때문에 SQL Server가 최악을 가정하는 것은 불행합니다. 즉, 스키마주기 == 데이터주기. 실제로 RI 제약 조건을 사용하는 경우 실제로 데이터에주기를 구축 할 수 없습니다!

다중 경로 문제가 비슷한 것 같습니다. 즉, 스키마의 다중 경로가 반드시 데이터의 다중 경로를 의미하지는 않지만 다중 경로 문제에 대한 경험이 적습니다.

SQL 서버가있는 경우 물론 않았다 여전히 (32)의 깊이 대상이 될 것 사이클을 허용하지만 아마 대부분의 경우에 적합합니다. (그러나 데이터베이스 설정이 아닙니다.

“삭제 대신”트리거도 작동하지 않습니다. 두 번째로 테이블을 방문하면 트리거가 무시됩니다. 따라서 캐스케이드를 실제로 시뮬레이트하려면주기가있는 경우 스토어드 프로 시저를 사용해야합니다. 그러나 Delete-Trigger는 다중 경로의 경우 작동합니다.

Celko는주기를 도입하지 않지만 상충 관계가있는 계층을 나타내는 “더 나은”방법을 제안합니다.


답변

트리거를 사용하여 여러 삭제 경로를 수행하는 방법을 설명하는 기사가 있습니다. 복잡한 시나리오에 유용 할 수 있습니다.

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


답변

그 소리에 따라 기존 외래 키 중 하나에 대해 OnDelete / OnUpdate 작업이 수행되어 코드 테이블이 수정됩니다.

이 외래 키를 만들면 주기적 문제가 발생합니다.

예 : 직원 업데이트, 업데이트시 작업에 의해 코드가 변경되고, 업데이트시 작업에 의해 직원이 변경됩니다.

두 테이블에 대한 테이블 정의와 외래 키 / 제약 정의를 게시하면 문제의 위치를 ​​알려줄 수 있습니다.


답변

이는 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);


답변

트리거는이 문제에 대한 해결책입니다.

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