web-dev-qa-db-ja.com

外部キー制約は、サイクルまたは複数のカスケードパスを引き起こす可能性がありますか?

テーブルに制約を追加しようとすると問題が発生します。エラーが表示されます:

テーブル 'Employee'にFOREIGN KEY制約 'FK74988DB24B3C886'を導入すると、サイクルまたは複数のカスケードパスが発生する場合があります。 ON DELETE NO ACTIONまたはON UPDATE NO ACTIONを指定するか、他の外部キー制約を変更します。

私の制約は、Codeテーブルとemployeeテーブルの間にあります。 Codeテーブルには、IdNameFriendlyNameType、およびValueが含まれます。 employeeにはコードを参照するフィールドがいくつかあるため、コードのタイプごとに参照を設定できます。

参照されるコードが削除された場合、フィールドをnullに設定する必要があります。

どのようにこれを行うことができますか?

162

SQL Serverはカスケードパスの単純なカウントを行い、実際にサイクルが存在するかどうかを判断するのではなく、最悪の事態を想定し、参照アクション(CASCADE)の作成を拒否します。参照アクションなしで制約を作成することはできます。デザインを変更できない場合(または変更すると問題が発生する場合)、最後の手段としてトリガーの使用を検討する必要があります。

FWIWカスケードパスの解決は複雑な問題です。他のSQL製品は単純に問題を無視し、サイクルを作成できるようにします。その場合、おそらくデザイナーの無知に合わせて、値を最後に上書きするかどうかを確認する競争が行われます(たとえば、ACE/Jetはこれを行います)。一部のSQL製品は単純なケースを解決しようとすることを理解しています。事実は残っており、SQL Serverは試行すらしていません。複数のパスを許可しないことで、非常に安全に動作します。

Microsoft自身 アドバイス FK制約の代わりにトリガーを使用します。

168
onedaywhen

複数のカスケードパスを使用する一般的な状況は次のようになります。2つの詳細を持つマスターテーブル。たとえば、「マスター」と「Detail1」と「Detail2」です。両方の詳細はカスケード削除です。これまでのところ問題はありません。しかし、両方の詳細に他のテーブル(「SomeOtherTable」など)と1対多の関係がある場合はどうでしょう。 SomeOtherTableには、Detail1ID列とDetail2ID列があります。

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

つまり、SomeOtherTableの一部のレコードはDetail1-recordsにリンクされており、SomeOtherTableの一部のレコードはDetail2レコードにリンクされています。 SomeOtherTable-recordsが両方のDetailsに決して属さないことが保証されていても、MasterからSomeOtherTableへの複数のカスケードパスがあるため(1つはDetail2経由、もう1つはDetail2経由)、SomeOhterTableのレコードを両方の詳細に対してカスケード削除することはできません。今、あなたはすでにこれを理解しているかもしれません。考えられる解決策は次のとおりです。

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

すべてのIDフィールドは、キーフィールドおよび自動インクリメントです。重要な点は、詳細テーブルのDetailMainIdフィールドにあります。これらのフィールドは、キーと参照の両方の制約です。マスターレコードのみを削除することにより、すべてをカスケード削除することが可能になりました。欠点は、detail1レコードごとに、detail2レコードごとに、DetailMain-record(正確で一意のIDを取得するために実際に最初に作成される)も存在する必要があることです。

86
hans riesebos

(機能的に)SCHEMAとDATAのサイクルおよび/または複数のパスの間に大きな違いがあることを指摘します。 DATAのサイクルおよびおそらくマルチパスは処理を複雑にし、パフォーマンスの問題(「適切な」処理のコスト)を引き起こす可能性がありますが、スキーマのこれらの特性のコストはゼロに近いはずです。

RDBのほとんどの明らかなサイクルは階層構造(組織図、パーツ、サブパーツなど)で発生するため、SQL Serverが最悪の事態を想定するのは残念です。つまり、スキーマサイクル==データサイクルです。実際、RI制約を使用している場合、データにサイクルを実際に構築することはできません!

マルチパスの問題は似ていると思います。つまり、スキーマ内の複数のパスが必ずしもデータ内の複数のパスを意味するわけではありませんが、マルチパスの問題に関する経験はあまりありません。

もちろん、SQL Server didがサイクルを許可する場合、深さ32の影響を受ける可能性がありますが、ほとんどの場合はおそらくこれで十分です。 (しかし、それはデータベース設定ではありません!)

「削除の代わり」トリガーも機能しません。テーブルが2回目にアクセスされると、トリガーは無視されます。したがって、カスケードを本当にシミュレートする場合は、サイクルが存在する状態でストアドプロシージャを使用する必要があります。ただし、マルチパスの場合には、Instead-of-Delete-Triggerが機能します。

Celkoは、サイクルを導入しない階層を表現する「より良い」方法を提案しますが、トレードオフがあります。

12
Bill Cohagan

トリガーを使用して複数の削除パスを実行する方法を説明した記事があります。これは複雑なシナリオに役立つかもしれません。

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

7
Javier

その音により、既存の外部キーの1つでOnDelete/OnUpdateアクションがあり、コードテーブルが変更されます。

したがって、この外部キーを作成すると、周期的な問題が発生します。

例えば。従業員を更新すると、更新時アクションによってコードが変更され、更新時アクションによって従業員が変更されます...など...

両方のテーブルのテーブル定義と外部キー/制約の定義を投稿すると、問題の場所を確認できるはずです...

3
Eoin Campbell

トリガーはこの問題の解決策です。

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
1
Tone Škoda

これは、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
RAJ

これは、データベーストリガーポリシータイプのエラーです。 トリガーはコードであり、カスケード削除のようなカスケードリレーションにいくつかのインテリジェンスまたは条件を追加できます。このような関連テーブルオプションを特化する必要がある場合があります

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

または、この機能を完全にオフにします。

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();