I think I have captured what you need in this basic design:
ElementBase
Self-fk for the hierarchy:
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
Cascade delete from 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
No cascading delete from ElementBase
; SET NULL
cascading delete from 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),);
CData
No cascading delete from ElementBase
; SET NULL
cascading delete from 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),);
S
Cascading delete from 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 Instead of Delete Trigger
This handles deleting related items within ElementBase
, then cascading deletes to AData
and CData
. Cascade deletes to BData
and S
are handled by RI:
CREATE OR ALTER TRIGGER [dbo.ElementBase IOD Cascade]ON dbo.ElementBaseINSTEAD OF DELETE ASBEGIN 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;