上記は私のデータ構造の図です。これは、「A」、「B」、「C」の3つの異なるタイプの「要素」を含むことができる階層を表します。関係は、可能であればlikeを使用して削除のカスケード動作を示しています。
階層内の位置(親とインデックス)や要素のタイプを示す列など、すべてのタイプに共通のプロパティがあります。これらの一般的な列は、ElementBase
テーブルに格納されます。
要素の各タイプには、要素タイプに基づいて対応するテーブルに格納される固有のプロパティもあります。
AData
、BData
、CData
の各行は、ElementBase
の一意のメイン行を参照します。 「A」および「C」要素もそれぞれ「B」要素を参照します。 「B」要素は0個以上の「S」を持つことができます。
私の問題は、参照の整合性を維持し、カスケード削除などをサポートするにはどうすればよいですか?
ElementBase
から行を削除し、AData
、BData
またはCData
の対応する行も削除できるようにしたいのですが。たとえば、「B」タイプの要素がElementBase
から削除された場合、最初にBData
の対応する行を削除する必要があります。次に、それを参照するすべての「C」タイプの要素がElementBase
とCData
の両方のテーブルで削除する必要があります。すべての「A」タイプの要素の参照は、NULL
のAData
に設定する必要があります。
そして、一番上のチェリー:削除する要素に任意のタイプの子がある場合、この同じロジックを再帰的に階層構造で実行したいと思います。
ElementBase
は自己参照なので、そのテーブルで単純なON DELETE CASCADE
機能を使用できません。また、AData
やCData
でも使用できません。どちらもBData
を参照しているため、SQL Serverで明らかに悪質な「複数のカスケードパス」が発生する可能性があります。
私が見つけた1つの代替案は、INSTEAD OF
トリガーです。その問題は、この動作が再帰的である必要があり、それらを再帰的にして、最終的には最後に元の削除を行う方法を完全に理解することができません。
この基本的な設計で必要なものを取り込んだと思います。
階層の自己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)
);
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
);
ElementBase
からのカスケード削除はありません。 SET NULL
BData
からのカスケード削除:
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),
);
ElementBase
からのカスケード削除はありません。 SET NULL
BData
からのカスケード削除:
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),
);
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
内の関連アイテムの削除を処理し、次にAData
とCData
にカスケード削除を処理します。 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;