Упорядочивание братьев и сестер в модели вложенного набора

#mysql #siblings #nested-set-model

#mysql #братья и сестры #модель вложенного набора

Вопрос:

Я столкнулся с проблемой с моделью вложенного набора, с MySQL. Я могу вставлять, удалять, перемещать поддеревья к другому родительскому элементу, все работает нормально.

Но я не могу понять, как упорядочить братьев и сестер. Например, у меня есть эти братья и сестры :

A, B, C, D, E

И я хочу переместить B после D, получив это :

A, C, D, B, E

Я нашел тонны хранимых процедур для вставки, удаления и т.д., Но ни одной для упорядочивания братьев и сестер. Единственное, что я нашел, это процедура для замены братьев и сестер, но это не то, чего я хочу достичь.

Я пытался написать свой собственный, но он кажется сложным и работает не во всех случаях.

Если вы знаете, как переместить узел до или после одного из его братьев и сестер, это было бы очень ценно.

Комментарии:

1. вы всегда хотите переключать B после D . я прав

2. Нет, это может быть что угодно другое: перемещение E после B, C перед A и так далее.

Ответ №1:

Итак… Я все переписал, и вот хранимая процедура, которая хорошо работает для перемещения одного узла после одного из его братьев и сестер. Если мы хотим переместить узел на первую позицию, просто передайте родительский идентификатор вместо идентификатора родственного узла.

 DELIMITER |
-- sibling parameter is either :
-- - the sibling id after which we want to put the page
-- - the parent id if we want to put the page on the first position
CREATE PROCEDURE move_after_sibling (IN to_move_id INT(10), IN parent_id INT(10), IN sibling_id INT(10))
LANGUAGE SQL
DETERMINISTIC
BEGIN
    DECLARE to_move_lft INT(10);
    DECLARE to_move_rgt INT(10);
    DECLARE parent_lft INT(10);
    DECLARE parent_rgt INT(10);
    DECLARE sibling_lft INT(10);
    DECLARE sibling_rgt INT(10);

    SET to_move_lft = (SELECT lft FROM pages WHERE id = to_move_id);
    SET to_move_rgt = (SELECT rgt FROM pages WHERE id = to_move_id);
    SET parent_lft = (SELECT lft FROM pages WHERE id = parent_id);
    SET parent_rgt = (SELECT rgt FROM pages WHERE id = parent_id);
    SET sibling_lft = (SELECT lft FROM pages WHERE id = sibling_id);
    SET sibling_rgt = (SELECT rgt FROM pages WHERE id = sibling_id);

    UPDATE pages 
        SET
            lft = 
                CASE 
                    WHEN sibling_id = parent_id THEN
                        CASE
                            WHEN lft BETWEEN parent_lft 1 AND to_move_lft-1 THEN
                                lft   (to_move_rgt - to_move_lft)   1
                            WHEN lft BETWEEN to_move_lft AND to_move_rgt THEN 
                                lft - (to_move_lft - (parent_lft   1))
                            ELSE
                                lft
                        END
                    ELSE
                        CASE 
                            WHEN to_move_lft > sibling_lft THEN
                                CASE
                                    WHEN lft BETWEEN sibling_rgt AND to_move_lft-1 THEN
                                        lft   (to_move_rgt - to_move_lft)   1
                                    WHEN lft BETWEEN to_move_lft AND to_move_rgt THEN 
                                        lft - (to_move_lft - (sibling_rgt   1))
                                    ELSE
                                        lft
                                END
                            ELSE
                                CASE
                                    WHEN lft BETWEEN to_move_rgt 1 AND sibling_rgt THEN
                                        lft - ((to_move_rgt - to_move_lft)   1)
                                    WHEN lft BETWEEN to_move_lft AND to_move_rgt THEN 
                                        lft   (sibling_rgt - to_move_rgt)
                                    ELSE
                                        lft
                                END
                        END
                END,
            rgt = 
                CASE 
                    WHEN sibling_id = parent_id THEN
                        CASE
                            WHEN rgt BETWEEN parent_lft 1 AND to_move_lft-1 THEN
                                rgt   (to_move_rgt - to_move_lft)   1
                            WHEN rgt BETWEEN to_move_lft AND to_move_rgt THEN 
                                rgt - (to_move_lft - (parent_lft   1))
                            ELSE
                                rgt
                        END
                    ELSE
                        CASE 
                            WHEN to_move_rgt > sibling_lft THEN
                                CASE
                                    WHEN rgt BETWEEN sibling_rgt 1 AND to_move_lft-1 THEN
                                        rgt   (to_move_rgt - to_move_lft)   1
                                    WHEN rgt BETWEEN to_move_lft AND to_move_rgt THEN 
                                        rgt - (to_move_lft - (sibling_rgt   1))
                                    ELSE
                                        rgt
                                END
                            ELSE
                                CASE
                                    WHEN rgt BETWEEN to_move_rgt 1 AND sibling_rgt 1 THEN
                                        rgt - ((to_move_rgt - to_move_lft)   1)
                                    WHEN rgt BETWEEN to_move_lft AND to_move_rgt THEN 
                                        rgt   (sibling_rgt - to_move_rgt)
                                    ELSE
                                        rgt
                                END
                        END
                END
        WHERE lft BETWEEN parent_lft 1 AND parent_r>
END
|
DELIMITER ;
  

Возможно, это не самый красивый фрагмент кода, который мы когда-либо видели, но он отлично работает и, вероятно, намного эффективнее любого алгоритма сортировки, например.

Ответ №2:

Я обнаружил, что при работе с вложенными наборами с использованием триггеров лучше:

  1. Используйте триггеры before для блокировки строк по мере необходимости, чтобы избежать проблем с параллелизмом.
  2. Используйте триггеры after для переиндексации, чтобы обойти проблемы, связанные с массовой вставкой / массовым обновлением.
  3. Дождитесь срабатывания самого последнего триггера after, прежде чем что-либо переиндексировать.
  4. Если в этот момент вы обнаружите, что с помощью инструкции было вставлено / перемещено более одного узла, полностью переиндексируйте соответствующий диапазон — это намного быстрее, чем многократное переиндексирование целых фрагментов дерева.

В частности, что касается вашего вопроса, вы перемещаете узлы один за другим, используя хранимую процедуру, если я все правильно понимаю. Это не сильно отличается от изменения родительского узла: найдите его новый индекс lft / rgt и сдвиньте его (и его дочерние узлы) соответственно, влево или вправо. Не забудьте сместить значения lft / rgt, если они сдвинуты вправо.

Кроме того, я подозреваю, что вы только начинаете определять потенциальные проблемы, которые вам нужно будет решить. По моему опыту, сложнее всего иметь дело с перестановками узлов с использованием одного обновления, например:

 A - B - C

D - E - F
  

Для:

 B - E - C

A - D - F
  

Комментарии:

1. Спасибо за ваш ответ, я думал о том же самом: смещение значений lft / rgt узла, который мы хотим переместить, и других затронутых узлов, конечно. Я переписал свою хранимую процедуру с нуля, и я придумал что-то очень похожее на мою предыдущую попытку. Но на этот раз это работает! Я предполагаю, что я где-то перепутал с чем-то 1 или -1. Я собираюсь отредактировать свое сообщение с помощью этой процедуры (которая немного велика и, возможно, может быть оптимизирована).

2. О потенциальных проблемах: да, использование одного обновления для создания нескольких перестановок кажется действительно сложным… Но в моем случае мне нужно переместить только один узел после одного из его братьев и сестер, так что, в конце концов, это довольно просто. Я не думаю, что с этим есть какие-либо потенциальные проблемы, или, по крайней мере, я не вижу их прямо сейчас. Вы думаете, я чего-то здесь не понимаю?

3. Немного: вставка / обновление / удаление одного узла — довольно простые случаи. В качестве дополнительного замечания рассмотрите возможность удаления свойств (rgt — lft — 1) / 2 = num_children и используйте округленные значения с плавающей запятой вместо целых чисел. Это значительно ускоряет вставки и обновления (удаления тривиальны: ничего не нужно делать); округление решает потенциальные проблемы с точностью, присущие числам с плавающей точкой; при этом вам нужно только локально переиндексировать здесь и там, когда у вас заканчивается пространство точности.

4. Я немного сбит с толку… Если мы оставим lft и rgt как есть после удаления, как мы узнаем, например, сколько дочерних элементов имеет узел?

5. Вы не можете, согласно моему предыдущему комментарию: «рассмотрите возможность удаления свойств (rgt — lft — 1) / 2 = num_children и используйте округленные значения с плавающей точкой вместо целых чисел».