web-dev-qa-db-ja.com

自己参照テーブルを含む複雑な削除カスケード

Diagram

上記は私のデータ構造の図です。これは、「A」、「B」、「C」の3つの異なるタイプの「要素」を含むことができる階層を表します。関係は、可能であればlikeを使用して削除のカスケード動作を示しています。

階層内の位置(親とインデックス)や要素のタイプを示す列など、すべてのタイプに共通のプロパティがあります。これらの一般的な列は、ElementBaseテーブルに格納されます。

要素の各タイプには、要素タイプに基づいて対応するテーブルに格納される固有のプロパティもあります。

ADataBDataCDataの各行は、ElementBaseの一意のメイン行を参照します。 「A」および「C」要素もそれぞれ「B」要素を参照します。 「B」要素は0個以上の「S」を持つことができます。

私の問題は、参照の整合性を維持し、カスケード削除などをサポートするにはどうすればよいですか?

ElementBaseから行を削除し、ADataBDataまたはCDataの対応する行も削除できるようにしたいのですが。たとえば、「B」タイプの要素がElementBaseから削除された場合、最初にBDataの対応する行を削除する必要があります。次に、それを参照するすべての「C」タイプの要素がElementBaseCDataの両方のテーブルで削除する必要があります。すべての「A」タイプの要素の参照は、NULLADataに設定する必要があります。

そして、一番上のチェリー:削除する要素に任意のタイプの子がある場合、この同じロジックを再帰的に階層構造で実行したいと思います。

ElementBaseは自己参照なので、そのテーブルで単純なON DELETE CASCADE機能を使用できません。また、ADataCDataでも使用できません。どちらもBDataを参照しているため、SQL Serverで明らかに悪質な「複数のカスケードパス」が発生する可能性があります。

私が見つけた1つの代替案は、INSTEAD OFトリガーです。その問題は、この動作が再帰的である必要があり、それらを再帰的にして、最終的には最後に元の削除を行う方法を完全に理解することができません。

4
Keith Stein

この基本的な設計で必要なものを取り込んだと思います。

db <>フィドル

ElementBase

階層の自己fk:

CREATE TABLE dbo.ElementBase
(
    id integer NOT NULL,
    parent_id integer NOT NULL,
    element_type char(1) NOT NULL,

    -- id key
    CONSTRAINT [PK dbo.ElementBase id]
        PRIMARY KEY CLUSTERED (id),

    -- fk target
    CONSTRAINT [UQ dbo.ElementBase id, element_type]
        UNIQUE NONCLUSTERED (id, element_type),

    -- self fk
    CONSTRAINT [FK dbo.ElementBase parent_id id]
        FOREIGN KEY (parent_id)
        REFERENCES dbo.ElementBase (id),

    -- valid element types
    CONSTRAINT [CK dbo.ElementBase element_type]
        CHECK (element_type IN ('a', 'b', 'c')),

    -- for maintenance
    INDEX [IX dbo.ElementBase parent_id] 
        NONCLUSTERED (parent_id)
);

BData

ElementBaseからのカスケード削除:

CREATE TABLE dbo.BData
(
    id integer NOT NULL,
    element_type AS CONVERT(char(1), 'b') PERSISTED,

    -- id key
    CONSTRAINT [PK dbo.BData id]
        PRIMARY KEY CLUSTERED (id),

    -- fk to ElementBase
    CONSTRAINT [FK Bdata ElementBase id, element_type]
        FOREIGN KEY (id, element_type)
        REFERENCES dbo.ElementBase (id, element_type)
        ON DELETE CASCADE
);

AData

ElementBaseからのカスケード削除はありません。 SET NULLBDataからのカスケード削除:

CREATE TABLE dbo.AData
(
    id integer NOT NULL,
    element_type AS CONVERT(char(1), 'a') PERSISTED,
    b_element integer NULL,

    -- id key
    CONSTRAINT [PK dbo.AData id]
        PRIMARY KEY CLUSTERED (id),

    -- fk to ElementBase
    CONSTRAINT [FK Adata ElementBase id, element_type]
        FOREIGN KEY (id, element_type)
        REFERENCES dbo.ElementBase (id, element_type)
        ON DELETE NO ACTION,

    -- fk to BData
    CONSTRAINT [FK dbo.AData dbo.BData id b_element]
        FOREIGN KEY (b_element)
        REFERENCES dbo.BData (id)
        ON DELETE SET NULL,

    -- fk lookup
    INDEX [IDX dbo.AData b_element] 
        NONCLUSTERED (b_element),
);

CData

ElementBaseからのカスケード削除はありません。 SET NULLBDataからのカスケード削除:

CREATE TABLE dbo.CData
(
    id integer NOT NULL,
    element_type AS CONVERT(char(1), 'c') PERSISTED,
    b_element integer NOT NULL,

    -- id key
    CONSTRAINT [PK dbo.CData id]
        PRIMARY KEY CLUSTERED (id),

    -- fk to ElementBase
    CONSTRAINT [FK Cdata ElementBase]
        FOREIGN KEY (id, element_type)
        REFERENCES dbo.ElementBase (id, element_type)
        ON DELETE NO ACTION,

    -- fk to BData
    CONSTRAINT [FK dbo.CData dbo.BData b_element id]
        FOREIGN KEY (b_element)
        REFERENCES dbo.BData (id)
        ON DELETE CASCADE,

    -- fk lookup
    INDEX [IDX dbo.CData b_element] 
        NONCLUSTERED (b_element),
);

S

BDataからのカスケード削除:

CREATE TABLE dbo.S
(
    s_id integer NOT NULL,
    b_element integer NOT NULL,

    -- id key
    CONSTRAINT [PK dbo.S s_id]
        PRIMARY KEY CLUSTERED (s_id),

    -- fk to BData
    CONSTRAINT [FK dbo.S dbo.BData b_element id]
        FOREIGN KEY (b_element)
        REFERENCES dbo.BData (id)
        ON DELETE CASCADE,

    -- fk lookup
    INDEX [IDX dbo.S b_element] 
        NONCLUSTERED (b_element),
);

削除トリガーの代わりにElementBase

これは、ElementBase内の関連アイテムの削除を処理し、次にADataCDataにカスケード削除を処理します。 BDataおよびSへのカスケード削除は、RIによって処理されます。

CREATE OR ALTER TRIGGER [dbo.ElementBase IOD Cascade]
ON dbo.ElementBase
INSTEAD OF DELETE AS
BEGIN
    SET ROWCOUNT 0;
    SET NOCOUNT ON;

    -- Exit if no work to do
    IF NOT EXISTS (SELECT * FROM Deleted) RETURN;

    -- Holds ElementBase rows identified for deletion
    CREATE TABLE #ToDelete 
    (
        id integer PRIMARY KEY,
        element_type char(1) NOT NULL
    );

    -- Find all related ElementBase records
    WITH R AS
    (
        -- Anchor: parent ElementBase rows
        SELECT D.id, D.element_type
        FROM Deleted AS D

        UNION ALL

        -- Recursive: children
        SELECT EB.id, EB.element_type
        FROM R
        JOIN dbo.ElementBase AS EB
            ON EB.parent_id = R.id
            AND EB.id <> R.id
    )
    INSERT #ToDelete
        (id, element_type)
    SELECT DISTINCT 
        R.id,
        R.element_type
    FROM R
    OPTION (MAXRECURSION 0);

    -- Delete related CData records (manual cascade)
    DELETE CD
    FROM #ToDelete AS TD
    JOIN dbo.CData AS CD
        ON CD.id = TD.id
    WHERE
        TD.element_type = 'c';

    -- Delete related AData records (manual cascade)
    DELETE AD
    FROM #ToDelete AS TD
    JOIN dbo.AData AS AD
        ON AD.id = TD.id
    WHERE
        TD.element_type = 'a';

    -- Delete ElementBase (BData, S records via cascade)
    DELETE EB
    FROM #ToDelete AS TD
    JOIN dbo.ElementBase AS EB
        ON EB.id = TD.id;
END;
5
Paul White 9