С тех пор я узнал немного больше, и я считаю, что вклад @Devart можно немного упростить. Решение, которое я сейчас использую, показано ниже.
ПРИМЕЧАНИЕ. Это решение работает правильно, только если используются таблицы InnoDB, а не MyISAM. Я думаю, что это как-то связано с тем, как работает сортировка по умолчанию в MyISAM. (InnoDB использует первичный ключ, тогда как MyISAM использует порядок вставки.) В частности, в таблицах MyISAM два набора рангов, генерируемых в разделе «копировать внуков», не синхронизированы друг с другом. Добавление предложений SORT BY в соответствующие части, похоже, тоже не имеет значения.
CREATE PROCEDURE sp_copy(p_parent_id INT)
BEGIN
DECLARE new_parent_id INT;
-- copy parent
INSERT INTO parent(parent_data) SELECT parent_data FROM parent WHERE parent_id=p_parent_id;
SET new_parent_id:=LAST_INSERT_ID();
-- copy child(s)
INSERT INTO child(child_data, parent_id)
SELECT child_data, new_parent_id FROM child WHERE parent_id=p_parent_id;
-- copy grandchild(s)
SET @rank1:=0;
SET @rank2:=0;
INSERT INTO grandchild(grandchild_data, child_id) SELECT gc.grandchild_data, c2.child_id FROM
(SELECT child_id, @rank1:=@rank1+1 as rank FROM child WHERE parent_id=p_parent_id) c1
INNER JOIN
(SELECT child_id, @rank2:=@rank2+1 as rank FROM child WHERE parent_id=new_parent_id) c2 ON c1.rank=c2.rank
INNER JOIN grandchild gc ON c1.child_id=gc.child_id;
END
Кроме того, для обработки таблицы правнуков можно использовать тот же принцип копирования копий записей внуков. Единственная дополнительная сложность заключается в добавлении объединения в каждый из двух подзапросов. Это необходимо, потому что поле child.parent_id требует ссылки в предложении WHERE:
-- copy greatgrandchild(s)
SET @rank1:=0;
SET @rank2:=0;
INSERT INTO greatgrandchild(greatgrandchild_data, grandchild_id) SELECT ggc.greatgrandchild_data, gc2.grandchild_id FROM
(SELECT grandchild_id, @rank1:=@rank1+1 as rank FROM grandchild INNER JOIN child ON child.child_id=grandchild.child_id WHERE parent_id=p_parent_id) gc1
INNER JOIN
(SELECT grandchild_id, @rank2:=@rank2+1 as rank FROM grandchild INNER JOIN child ON child.child_id=grandchild.child_id WHERE parent_id=new_parent_id) gc2 ON gc1.rank=gc2.rank
INNER JOIN greatgrandchild ggc ON gc1.grandchild_id=ggc.grandchild_id;