I have the following table:
CREATE TABLE X (
A SOMETYPE NOT NULL,
B SOMETYPE NOT NULL,
C SOMETYPE NULL,
PRIMARY KEY (A,B),
FOREIGN KEY (A,C) REFERENCES X (A,B)
);
The entities stored in X are hierarchically organized: If a row (A1,B1,C1) exists and C1 IS NOT NULL then it is considered to be a “child” of (A1,C1,C2) whatever C2 is. Since an item cannot descend from itself, I would like to make it illegal that circular hierarchical sequences exist:
-- legal
INSERT INTO X (A1,B1,NULL);
INSERT INTO X (A1,B2,B1);
INSERT INTO X (A1,B3,B2);
INSERT INTO X (A1,B4,B2);
-- currently legal, but I want to make it illegal
UPDATE X SET C = B1 WHERE B = B1; /* B1-B1 */
UPDATE X SET C = B2 WHERE B = B1; /* B1-B2-B1 */
UPDATE X SET C = B3 WHERE B = B1; /* B1-B2-B3-B1 */
UPDATE X SET C = B4 WHERE B = B1; /* B1-B2-B4-B1 */
UPDATE X SET C = B2 WHERE B = B2; /* B2-B2 */
UPDATE X SET C = B3 WHERE B = B2; /* B2-B3-B2 */
UPDATE X SET C = B4 WHERE B = B2; /* B2-B4-B2 */
UPDATE X SET C = B3 WHERE B = B3; /* B3-B3 */
UPDATE X SET C = B4 WHERE B = B4; /* B4-B4 */
How do I do this?
Alternatively, I could add a field representing the “level” in the hierarchy to the table:
CREATE TABLE X (
A SOMETYPE NOT NULL,
B SOMETYPE NOT NULL,
C SOMETYPE NULL,
LEVEL INT NOT NULL,
PRIMARY KEY (A,B),
FOREIGN KEY (A,C) REFERENCES X (A,B)
);
Then I would like to require that LEVEL be 0 when C IS NULL, and parent's LEVEL + 1 otherwise.
I am using SQL Server 2008 R2.
To check for circular references i have used a trigger and recursive CTE:
Now, we can run some tests:
Results:
For the second test, you can see how this trigger has canceled (
ROLLBACK TRANSACTION) the transaction and, after UPDATE, nothing has been executed (in current batch).