用于删除记录的MySQL递归过程

时间:2016-06-28 13:54:53

标签: mysql recursive-query

我有一个表Models,包含这些(相关)属性:

-- -----------------------------------------------------
-- Table `someDB`.`Models`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `someDB`.`Models` (
  `model_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `type_id` SMALLINT UNSIGNED NOT NULL,
  -- someOtherAttributes
  PRIMARY KEY (`model_id`),
ENGINE = InnoDB;


+---------+---------+
| model_id| type_id |
+---------+---------+
| 1       | 4       | 
| 2       | 4       | 
| 3       | 5       | 
| 4       | 3       | 
+---------+---------+

表格Model_Hierarchy显示父母&子关系(再次,仅显示相关属性):

-- -----------------------------------------------------
-- Table `someDB`.`Model_Hierarchy`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `someDB`.`Model_Hierarchy` (
  `parent_id` MEDIUMINT UNSIGNED NOT NULL,
  `child_id` MEDIUMINT UNSIGNED NOT NULL,
  -- someOtherAttributes,

  INDEX `fk_Model_Hierarchy_Models1_idx` (`parent_id` ASC),
  INDEX `fk_Model_Hierarchy_Models2_idx` (`child_id` ASC),
  PRIMARY KEY (`parent_id`, `child_id`),
  CONSTRAINT `fk_Model_Hierarchy_Models1`
    FOREIGN KEY (`parent_id`)
    REFERENCES `someDB`.`Models` (`model_id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_Model_Hierarchy_Models2`
    FOREIGN KEY (`child_id`)
    REFERENCES `someDB`.`Models` (`model_id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


+-----------+----------+
| parent_id | child_id |
+-----------+----------+
| 1         | 2        | 
| 2         | 4        | 
| 3         | 4        | 
+-----------+----------+

如果某个模型不是另一个类型为5的模型的父项或子项(在某些时候),则该模型无效,因此应删除。

这意味着应删除模型12,因为他们在任何时候都没有将模型作为父项或子项type_id = 5

此层次结构中有N个级别,但没有循环关系(即。1 - > 2; 2 - > 1将不存在)。

关于如何做到这一点的任何想法?

1 个答案:

答案 0 :(得分:1)

评论分散在整个代码中。

<强>架构:

CREATE TABLE `Models`
(   -- Note that for now the AUTO_INC is ripped out of this for ease of data insertion
    -- otherwise we lose control at this point (this is just a test)
    -- `model_id` MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `model_id` MEDIUMINT UNSIGNED PRIMARY KEY,
    `type_id` SMALLINT UNSIGNED NOT NULL
)ENGINE = InnoDB;

CREATE TABLE `Model_Hierarchy`
(   -- OP comments state these are more like components
    --
    -- @Drew imagine b being a product and a and c being two different ways to package it. 
    -- Hence b is contained in both a and c respectively and separately (ie. customer can buy 
    -- both a and c), however, any change (outside of the scope of this question) to b is 
    -- reflected to both a and c. `Model_Hierarchy can be altered, yes (the project is 
    -- in an early development). Max tree depth is unknown (this is for manufacturing... 
    -- so a component can consist of a component... that consist of further component etc. 
    -- no real limit). How many rows? Depends, but I don't expect it to exceed 2^32.
    --
    --
    -- Drew's interpretation of the the above: `a` is a parent of `b`, `c` is a parent of `b`
    --
    `parent_id` MEDIUMINT UNSIGNED NOT NULL,
    `child_id` MEDIUMINT UNSIGNED NOT NULL,

    INDEX `fk_Model_Hierarchy_Models1_idx` (`parent_id` ASC),
    INDEX `fk_Model_Hierarchy_Models2_idx` (`child_id` ASC),
    PRIMARY KEY (`parent_id`, `child_id`),
    key(`child_id`,`parent_id`), -- NoteA1 pair flipped the other way (see NoteA2 in stored proc)

    CONSTRAINT `fk_Model_Hierarchy_Models1`
    FOREIGN KEY (`parent_id`)
    REFERENCES `Models` (`model_id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION,

    CONSTRAINT `fk_Model_Hierarchy_Models2`
    FOREIGN KEY (`child_id`)
    REFERENCES `Models` (`model_id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
)ENGINE = InnoDB;

CREATE TABLE `GoodIds`
(   -- a table to determine what not to delete from models
    `id` int auto_increment primary key,
    `model_id` MEDIUMINT UNSIGNED,
    `has_been_processed` int not null,
    dtFinished datetime null,
    -- index section (none shown, developer chooses later, as he knows what is going on)
    unique index(model_id), -- supports the "insert ignore" concept
    -- FK's below:
    foreign key `fk_abc_123` (model_id) references Models(model_id)
)ENGINE = InnoDB;

从顶部开始放弃并重新开始:

-- ------------------------------------------------------------
-- reverse order is happier
drop table `GoodIds`;
drop table `Model_Hierarchy`;
drop table `Models`;
-- ------------------------------------------------------------

加载测试数据:

insert Models(model_id,type_id) values
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,5),(10,1),(11,1),(12,1);
-- delete from Models; -- note, truncate does not work on parents of FK's

insert Model_Hierarchy(parent_id,child_id) values
(1,2),(1,3),(1,4),(1,5),
(2,1),(2,4),(2,7),
(3,2),
(4,8),(4,9),
(5,1),
(6,1),(6,2),
(7,1),(7,10),
(8,1),(8,12),
(9,11),
(10,11),
(11,12);

-- Set 2 to test (after a truncate / copy paste of this below to up above):
(1,2),(1,3),(1,4),(1,5),
(2,1),(2,4),(2,7),
(3,2),
(4,8),(4,9),
(5,1),
(6,1),(6,2),
(7,1),(7,10),
(8,1),(8,12),
(9,1),
(10,11),
(11,12);
-- truncate table Model_Hierarchy;
-- select * from Model_Hierarchy;
-- select * from Models where type_id=5;

存储过程:

DROP PROCEDURE if exists loadUpGoodIds;
DELIMITER $$
CREATE PROCEDURE loadUpGoodIds()
BEGIN
    DECLARE bDone BOOL DEFAULT FALSE;
    DECLARE iSillyCounter int DEFAULT 0;

    TRUNCATE TABLE GoodIds;
    insert GoodIds(model_id,has_been_processed) select model_id,0 from Models where type_id=5;    
    WHILE bDone = FALSE DO
        select min(model_id) into @the_Id_To_Process from GoodIds where has_been_processed=0;
        IF @the_Id_To_Process is null THEN
            SET bDone=TRUE;
        ELSE
            -- First, let's say this is the parent id.
            -- Find the child id's that this is a parent of
            -- and they qualify as A Good Id to save into our Good table 
            insert ignore GoodIds(model_id,has_been_processed,dtFinished) 
            select child_id,0,null
            from Model_Hierarchy
            where parent_id=@the_Id_To_Process;

            -- Next, let's say this is the child id.
            -- Find the parent id's that this is a child of
            -- and they qualify as A Good Id to save into our Good table 
            insert ignore GoodIds(model_id,has_been_processed,dtFinished) 
            select child_id,0,null
            from Model_Hierarchy
            where child_id=@the_Id_To_Process;

            -- NoteA2: see NoteA1 in schema
            -- you can feel the need for the flipped pair composite key in the above

            UPDATE GoodIds set has_been_processed=1,dtFinished=now() where model_id=@the_Id_To_Process;
        END IF;

        -- safety bailout during development:
        SET iSillyCounter = iSillyCounter + 1;
        IF iSillyCounter>10000 THEN
            SET bDone=TRUE;
        END IF;

    END WHILE;
END$$
DELIMITER ;

<强>测试

call loadUpGoodIds();

-- select count(*) from GoodIds; -- 9 / 11 / 12
select * from GoodIds limit 10;
+----+----------+--------------------+---------------------+
| id | model_id | has_been_processed | dtFinished          |
+----+----------+--------------------+---------------------+
|  1 |        9 |                  1 | 2016-06-28 20:33:16 |
|  2 |       11 |                  1 | 2016-06-28 20:33:16 |
|  4 |       12 |                  1 | 2016-06-28 20:33:16 |
+----+----------+--------------------+---------------------+

拨打电话,可以折叠成存储过程:

-- The below is what to run
-- delete from Models where model_id not in (select null); -- this is a safe call (will never do anything)
-- the above is just a null test

delete from Models where model_id not in (select model_id from GoodIds);
-- Error 1451: Cannot delete or update a parent row: a FK constraint is unhappy
-- hey the cascades did not work, can figure that out later
-- Let go bottom up for now. Meaning, to honor FK constraints, kill bottom up.
delete from Model_Hierarchy where parent_id not in (select model_id from GoodIds);
-- 18 rows deleted
delete from Model_Hierarchy where child_id not in (select model_id from GoodIds);
-- 0 rows deleted
delete from Models where model_id not in (select model_id from GoodIds);
-- 9 rows deleted / 3 remain
select * from Models;
+----------+---------+
| model_id | type_id |
+----------+---------+
|        9 |       5 |
|       11 |       1 |
|       12 |       1 |
+----------+---------+