Эта форма работает надежно:
SELECT T.`id`, T.`name`, T.`parent_id`
FROM `testapp_tag` T
WHERE NOT (T.`id` IN (
SELECT U0.`id`
FROM `testapp_tag` U0
LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`)
WHERE U1.`id` IS NULL))
ORDER BY T.`name` ASC;
Дополнительный фильтр NOT + IN +, кажется, выбрасывает MySQL. Это определенно ошибка.
Тест в NOT () ищет 2 части. Если первая часть истинна, вторая не может быть истинной, независимо от того, может ли поле быть пустым или нет. Это избыточное предложение, которое, похоже, является причиной ошибки.
Исходя из ответа ScrumMeister, я подтверждаю, что ошибка связана с каким-то видом кэширования для последнего вставленного идентификатора в AUTO_INCREMENT.
DROP TABLE IF EXISTS `testapp_tag`;
CREATE TABLE `testapp_tag` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`name` varchar(10) NOT NULL,
`parent_id` integer
);
start transaction;
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t1", NULL);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t2", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t3", 1);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t4", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t5", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t6", 3);
INSERT INTO `testapp_tag` (`name`, `parent_id`) VALUES ("t7", 3);
commit;
delete from testapp_tag where id = 6; #######
explain extended
SELECT T.`id`, T.`name`, T.`parent_id`
FROM `testapp_tag` T
WHERE NOT (T.`id` IN (
SELECT U0.`id`
FROM `testapp_tag` U0
LEFT OUTER JOIN `testapp_tag` U1 ON (U0.`id` = U1.`parent_id`)
WHERE U1.`id` IS NULL) AND T.`id` IS NOT NULL)
ORDER BY T.`name` ASC;
show warnings;
Производит этот план
select `test`.`t`.`id` AS `id`,`test`.`t`.`name` AS `name`,`test`.`t`.`parent_id` AS `parent_id`
from `test`.`testapp_tag` `T` where ((not(<in_optimizer>(`test`.`t`.`id`,
<exists>(select 1 AS `Not_used` from `test`.`testapp_tag` `U0` left join `test`.`testapp_tag` `U1`
on((`test`.`u1`.`parent_id` = `test`.`u0`.`id`)) where (isnull(`test`.`u1`.`id`)
and (<cache>(`test`.`t`.`id`) = `test`.`u0`.`id`)))))) **or (`test`.`t`.`id` = 7)**)
order by `test`.`t`.`name`
Если вставка останавливается на t6, а удаление также на t6, ошибка маскируется, потому что добавленное предложение равно или (test.t.id = 6), которое мы уже удалили в строке, помеченной #### ###