Subject Structure Chart - Moving subtree in a closure table
Author

I'm using a closure table to manage an organisation structure. The organisation table is defined as:

CREATE TABLE ORGANISATION 

(

  ORGID DOM_INTLARGE NOT NULL,

  ORG_NME DOM_VARCHARMEDIUM NOT NULL,

  LEVEL_NUM DOM_INTSMALL NOT NULL,

  INUSE DOM_BOOLN,

CONSTRAINT PK_ORGANISATION PRIMARY KEY (ORGID),

CONSTRAINT UN_ORGNME UNIQUE (ORG_NME, LEVEL_NUM)

);


and the closure table as:


CREATE TABLE ORGCHART 

(

  PARENTID DOM_INTLARGE NOT NULL,

  CHILDID DOM_INTLARGE NOT NULL,

  DEPTH DOM_INTSMALL NOT NULL,

CONSTRAINT PK_ORGCHART PRIMARY KEY (PARENTID, CHILDID),

CONSTRAINT FK_ORG2CHCHILD FOREIGN KEY (CHILDID) REFERENCES ORGANISATION (ORGID),

CONSTRAINT ORG2CHPARENT FOREIGN KEY (PARENTID) REFERENCES ORGANISATION (ORGID));


If I use the statements in the following stored procedure to move a part in the middle of the structure (where DEPTH =2 in the ORGCHART table) I get a PRIMARY or UNIQUE KEY constraint "PK_ORGCHART" constraint error as the delete statement hasn't removed all the required rows.


SET TERM ^ ;

CREATE PROCEDURE MOVEORGANISATION_OLD 

(

  IN_ORGTOMOVE BIGINT,

  IN_WHERETOMOVE BIGINT

)

AS

BEGIN 

DELETE FROM orgchart

WHERE childid IN (SELECT childid

FROM OrgChart

WHERE parentid = :IN_OrgToMove)

AND parentid IN (SELECT parentid

FROM OrgChart

WHERE childid = :IN_OrgToMove

AND parentid != childid);


INSERT INTO OrgChart (ParentId, ChildId, depth)

SELECT supertree.ParentId, subtree.ChildId, subtree.DEPTH + supertree.DEPTH + 1

FROM OrgChart supertree

CROSS JOIN OrgChart  subtree

WHERE supertree.ChildId = :IN_WhereToMove

AND subtree.ParentId = :IN_OrgToMove;


END

 ^


SET TERM ; ^



However if I first store a list of the rows to be delete from ORGCHART table into a temporary table defined as:


CREATE TABLE TMP_ORGCHANGE 

(

  ORGID DOM_INTLARGE,

  PARENTID DOM_INTLARGE

);


and then use it in the where clause criteria of the delete statement everything works as expected (see revise procedure below).

 

SET TERM ^ ;

CREATE PROCEDURE MOVEORGANISATION 

(

  IN_ORGTOMOVE BIGINT,

  IN_WHERETOMOVE BIGINT

)

AS

BEGIN

/*List records to be removed into temporary table */

insert into TMP_ORGCHANGE (parentid, orgid)

select parentid, childid FROM orgchart

WHERE childid IN (SELECT childid

FROM OrgChart

WHERE parentid = :IN_OrgToMove)

AND parentid IN (SELECT parentid

FROM OrgChart

WHERE childid = :IN_OrgToMove

AND parentid != childid);


/* now delete records  from ORGCHART */

delete from ORGCHART

where parentid in (select parentid from TMP_ORGCHANGE)

and   childid in (select orgid from TMP_ORGCHANGE);


/* create the new links */

INSERT INTO OrgChart (ParentId, ChildId, depth)

SELECT supertree.ParentId, subtree.ChildId, subtree.DEPTH + supertree.DEPTH + 1

FROM OrgChart supertree

CROSS JOIN OrgChart  subtree

WHERE supertree.ChildId = :IN_WhereToMove

AND subtree.ParentId = :IN_OrgToMove;

END

 ^

SET TERM ; ^


Is there something that I'm doing wrong in the first procedure that prevents all relevant records from being deleted.


Here is a sample dataset to illustrate. If I pass the values 5,2 to the first procedure the operation fails but passing them to the second procedure it succeeds. 


INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('1', 'Whole Organisation', '0', NULL, NULL);

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('2', 'Div1', '1', '1', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('3', 'Div2', '1', '1', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('4', 'Dir11', '2', '2', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('5', 'Dir21', '2', '3', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('6', 'Spec111', '3', '4', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('7', 'Spec112', '3', '4', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('8', 'Spec211', '3', '5', '1');

INSERT INTO ORGANISATION (ORGID, ORG_NME, LEVEL_NUM, PARENTID, INUSE) VALUES ('9', 'Spec212', '3', '5', '1');


INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '1', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('2', '2', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '2', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('3', '3', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '3', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('4', '4', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('2', '4', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '4', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('5', '5', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('3', '5', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '5', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('6', '6', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('4', '6', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('2', '6', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '6', '3');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('7', '7', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('4', '7', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('2', '7', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '7', '3');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('8', '8', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('5', '8', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('3', '8', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '8', '3');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('9', '9', '0');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('5', '9', '1');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('3', '9', '2');
INSERT INTO ORGCHART (PARENTID, CHILDID, DEPTH) VALUES ('1', '9', '3');