Древовидная структура! Прекратите использовать рекурсию, это лучшее решение быстрее! Сильнее! Лучше использовать!
Древовидная структура! Прекратите использовать рекурсию, это лучшее решение быстрее! Сильнее! Лучше использовать!

Привет всем, я Иханг;

Независимо от того, занимаетесь ли вы фронтенд- или бэкенд-разработкой,Тогда я считаю, что необходимость в древовидной структуре наверняка уже встречалась раньше.,Особенно проекты типа платформы управления,Обычно это строка меню с древовидной структурой.,Другой пример,Организационная структура компании,иерархические отношенияОтношения принадлежностиОжидание спроса,По сути, они являются проявлением древовидной структуры;

удовлетворить эту потребность,Самая распространенная и простая идея дизайна::отношения отца и сына,Дочерний элемент сохраняет свой родительский идентификатор через поле,Затем получите иерархические отношения посредством рекурсии;

Несколько дней назад некоторые друзья из группы технического обмена спросили: В реальном бизнесе в древовидной структуре слишком много данных, а уровни глубокие. После того как процесс запроса является рекурсивным, производительность относительно низкая. Есть ли что-нибудь. лучше? Идеи дизайна делают запросы и статистику более удобными и эффективными;

Сегодня я представлю вам новое решение для проектирования древовидной структуры.:Улучшен метод дерева предзаказа,Преимущества в запросах и статистике,Рекурсивное проектное решение намного шире, чем отношения родитель-потомок;

В этой статье будут подробно объяснены и сравнены методы реализации, преимущества и недостатки двух решений.

Каталог статей:

Что касается спроса на древовидные структуры, независимо от того, какой метод используется, проблемы, которые необходимо решить, одни и те же. Вот общие проблемы древовидных структур:

  • Добавление, удаление и изменение узлов
  • Есть ли дочерние узлы (листовые узлы)
  • Запросить все дочерние узлы
  • Запросить все дочерние узлы
  • Запросить все узлы-потомки
  • Запрос родительского узла
  • Запрос родительского узла
  • Подсчитайте количество всех дочерних отделов

В ответ на вышеперечисленные проблемы воспользуемся Организационной системой Простой. структура компании Пример,Давайте посмотрим,Как реализуются и решаются оба решения?

Вся информация в этой статье реализована с использованием MySQL+Java.,Основные идеи схожи,фактическое использование,Может быть основано на особенностях языкаа также Просто настройте его так, как вы привыкли。

1План отношений отца и сына

Отношения «родитель-потомок», как следует из названия, означают, что текущий узел обращает внимание только на то, кем является его родительский узел, и сохраняет его. Чтобы узнать, какие дочерние узлы у меня есть, мне нужно только глобально найти все элементы, чей родительский идентификатор согласован. с моим удостоверением личности;

Как показано ниже:

Возможности программы

  • преимущество
    • Решение легко понять
    • Структура данных Простой ясно
    • Иерархия интуитивно понятна и понятна.
    • Простота обслуживания иерархические отношения Просто нужнососредоточиться насобственный отецID,Поэтому при добавлении Исправлять,Как только отношения изменятся,Просто измените соответствующий родительский идентификатор.
  • недостаток
    • Проблемы с поиском, проблемы со статистикой По данным текущего узла можно получить только данные дочерних узлов. Как только запрос и статистика выходят за рамки родительского и дочернего узлов, поиск по ним можно производить только послойно посредством рекурсии;

Пример

Согласно приведенной выше схеме Пример, соответствующая структура таблицы выглядит следующим образом:

ID

dep_name (название отдела)

уровень

родительский_id (идентификатор отца)

1

совет директоров

1

0

2

Главный управляющий

2

1

3

совет директоровсекретарь

2

1

4

Отдел продукции

3

2

5

Административный директор

3

2

6

Проектный отдел

4

4

7

Технологический отдел

4

4

8

Финансовый отдел

4

5

9

Административный отдел

4

5

10

клиент

5

7

11

Сервер

5

7

SQL-скрипт:

Язык кода:javascript
копировать
DROP TABLE IF EXISTS `department_info`;
CREATE TABLE `department_info`  (
  `id` int(11) NOT NULL,
  `dep_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'имя',
  `level` int(11) NULL DEFAULT NULL,
  `parent_id` int(11) NULL DEFAULT NULL COMMENT «Идентификатор отца»,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `department_info` VALUES (1, 'совет директоров', 1, 0);
INSERT INTO `department_info` VALUES (2, 'Главный управляющий', 2, 1);
INSERT INTO `department_info` VALUES (3,'совет директоровсекретарь', 2, 1);
INSERT INTO `department_info` VALUES (4, 'Отдел продукции', 3, 2);
INSERT INTO `department_info` VALUES (5, 'Административный директор', 3, 2);
INSERT INTO `department_info` VALUES (6, 'Проектный отдел', 4, 4);
INSERT INTO `department_info` VALUES (7, 'Технологический отдел', 4, 4);
INSERT INTO `department_info` VALUES (8, 'Финансовый отдел', 4, 5);
INSERT INTO `department_info` VALUES (9, 'Административный отдел', 4, 5);
INSERT INTO `department_info` VALUES (10, 'клиент', 5, 7);
INSERT INTO `department_info` VALUES (11, 'Сервер', 5, 7);
Создание функций

Поскольку запрос родительских и дочерних узлов требует рекурсии, для удобства запроса здесь созданы две функции:

Функция для рекурсивного запроса идентификаторов узлов-потомков

Язык кода:javascript
копировать
DROP FUNCTION IF EXISTS queryChildrenDepInfo;
DELIMITER ;;
CREATE FUNCTION queryChildrenDepInfo(dep_id INT)
RETURNS VARCHAR(4000)
BEGIN
 DECLARE sTemp VARCHAR(4000);
 DECLARE sTempChd VARCHAR(4000);
 SET sTemp='$';
 SET sTempChd = CAST(dep_id AS CHAR);
 WHILE sTempChd IS NOT NULL DO
  SET sTemp= CONCAT(sTemp,',',sTempChd);
  SELECT GROUP_CONCAT(id) INTO sTempChd FROM department_info WHERE FIND_IN_SET(parent_id,sTempChd)>0;
 END WHILE;
 RETURN sTemp;
END
;;
DELIMITER ;

тест: Опросить все дочерние узлы в разделе «Технологический отдел»?

Язык кода:javascript
копировать
SELECT queryChildrenDepInfo(4);
Язык кода:javascript
копировать
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(4));

Функция для рекурсивного запроса идентификаторов узлов-предков

Язык кода:javascript
копировать
DROP FUNCTION IF EXISTS queryParentDepInfo;
DELIMITER;;
CREATE FUNCTION queryParentDepInfo(dep_id INT)
RETURNS VARCHAR(4000)
BEGIN
 DECLARE sTemp VARCHAR(4000);
 DECLARE sTempChd VARCHAR(4000);
 SET sTemp='$';
 SET sTempChd = CAST(dep_id AS CHAR);
 SET sTemp = CONCAT(sTemp,',',sTempChd);
 SELECT parent_id INTO sTempChd FROM department_info WHERE id = sTempChd;
 WHILE sTempChd <> 0 DO
  SET sTemp = CONCAT(sTemp,',',sTempChd);
  SELECT parent_id INTO sTempChd FROM department_info WHERE id = sTempChd;
 END WHILE;
 RETURN sTemp;
END
;;
DELIMITER ;

тест: Опросить все родительские узлы Технологического отдела?

Язык кода:javascript
копировать
SELECT queryParentDepInfo(7);
Язык кода:javascript
копировать
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(7));
Добавление, удаление и изменение узлов

Добавить узел

Например, вы хотите добавить отдел тестирования в раздел «Технологический отдел».

Язык кода:javascript
копировать
INSERT INTO department_info(`id`, `dep_name`, `level`, `parent_id`) VALUES (12, «Отдел испытаний», 5, 7);

Изменить узел

Например: изменить отдел тестирования (ID = 12) Поднимите и положите в Отдел. продукции(ID = 4) Далее вам нужно только изменить ID родительского узла, соответствующего тестовому отделу, на 4.

Язык кода:javascript
копировать
SET @id = 12;
SET @pid = 4;
UPDATE department_info SET `parent_id` = @pid WHERE `id` = @id;

Удалить узел

По сравнению с добавлением и изменением удаление является немного особенным. Если у удаленного узла есть дочерние узлы, это означает, что дочерние узлы также необходимо удалить одновременно;

Поэтому вам необходимо использовать функцию для рекурсивного запроса идентификаторов узлов-потомков (queryChildrenDepInfo), созданную выше.

например:удалить Технологический отдел Дверь;

Язык кода:javascript
копировать
DELETE FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(7));
Есть ли дочерние узлы (листовые узлы)

В рамках этой схемы существует два способа определить, является ли это листовым узлом:

Подсчитайте количество текущих узлов и узлов-потомков.

Рекурсивно запрашивать идентификаторы всех дочерних узлов.,и посчитаем количество,Поскольку запрос функции содержит сам узел,Итак, здесь использованоCOUNT(*)-1подсчитать количество дочерних узлов,Если равно 0, это листовой узел.,Если оно больше 0, это означает, что это не листовой узел;

Язык кода:javascript
копировать
-- Проверять Проектный Является ли отдел (ID=6) конечным узлом?
SET @id = 6;
-- Поскольку число включает в себя само себя и поскольку счетчик представляет собой количество дочерних узлов, для удаления самого себя необходимо -1.
SELECT COUNT(*)-1 FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));

Добавьте теги для конечных узлов

Добавьте в таблицу поле isLeaf. При добавлении, удалении или изменении узла используйте это поле, чтобы отметить, является ли он конечным узлом.

Опрос всех подчиненных отделов (подузлов)

Опросить все подчиненные отделы,В это время вам нужно использовать текущий узелизidиlevelПоле

пример:Запрос Отдел продукции(id = 4,level = 3) дочерние узлы

Язык кода:javascript
копировать
SET @id = 4;
SET @lv = 3;
SELECT * from department_info where parent_id = @id AND `level` = @lv + 1;
Запросить все подчиненные отделы (внучатые узлы)

В реальных бизнес-сценариях это требование встречается редко. Оно в основном используется для демонстрации работоспособности и не исключает его использования в особых случаях.

Запросить дочерний Узел гораздо более проблематичен, чем дочерние узлы, поскольку между текущим узлом и дочерним узлом нет связи данных, поэтому вам нужно использовать дочерние узлы для поиска внучатого узла, поэтому вы должны использовать функцию здесь. для рекурсивного запроса идентификаторов узлов-потомков Понятно;

пример:Запрос Отдел продукции(id = 4,level = 3) узел-внук

Язык кода:javascript
копировать
-- Запросить дочерний узел
SET @id = 4;
SET @lv = 3;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id)) AND `level` = @lv + 2;
Поиск по всем подчиненным отделам

Запрос подчиненных отделов аналогичен запросу подчиненных отделов (дочерних узлов), за исключением того, что нет необходимости проверять иерархию;

Пример: Запросить все подчиненные отделы в разделе «Отдел продукции»?

Язык кода:javascript
копировать
SET @id = 4;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));
Запросите отдел, к которому вы принадлежите

То есть для запроса родительского узла; в соответствии с этим решением идентификатор родительского узла сохранен в узле, и родительский узел можно получить непосредственно через идентификатор.

Опросить все вышестоящие отделы

Поскольку текущий узел сохраняет только идентификатор родительского узла, информацию более высокого уровня можно получить только уровень за уровнем посредством рекурсии;

пример:Запрос Технологический отдел(id = 7) Все вышестоящие отделы;

Язык кода:javascript
копировать
SET @id = 7;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(@id));
Посчитайте количество всех подчиненных отделов

Это то же самое, что запросить, является ли это листовым узлом, но способ интерпретации полученных данных другой;

Пример: Посчитайте количество подчиненных подразделений Технологического отдела?

Язык кода:javascript
копировать
SET @id = 7;
SELECT COUNT(*)-1 FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));

2Улучшена древовидная схема предзаказа.

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

Далее давайте представим улучшенную схему древовидной структуры дерева предварительного заказа.,Узел больше не сохраняет идентификатор родительского узла.,СкорееДобавьте lvalue и rvalue к каждому узлу

Как показано ниже:

Соответствующие табличные данные следующие:

id

dep_name (название отдела)

л(lзначение)

rt(rзначение)

лв(уровень)

1

совет директоров

1

22

1

2

Главный управляющий

2

19

2

3

совет директоровсекретарь

20

21

2

4

Отдел продукции

3

12

3

5

Административный директор

13

18

3

6

Проектный отдел

4

5

4

7

Технологический отдел

6

11

4

8

Финансовый отдел

14

15

4

9

Административный отдел

16

17

4

10

клиент

7

8

5

11

Сервер

9

10

5

SQL-заявление:

Язык кода:javascript
копировать
DROP TABLE IF EXISTS `department_info2`;
CREATE TABLE `department_info2`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dep_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'имя',`lt` int(11) NOT NULL COMMENT 'узел Номер слева',`rt` int(11) NOT NULL COMMENT 'узел Правильный номер',
  `lv` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `department_info2` VALUES (1, 'совет директоров', 1, 22, 1);
INSERT INTO `department_info2` VALUES (2, 'Главный управляющий', 2, 19, 2);
INSERT INTO `department_info2` VALUES (3,'совет директоровсекретарь', 20, 21, 2);
INSERT INTO `department_info2` VALUES (4, 'Отдел продукции', 3, 12, 3);
INSERT INTO `department_info2` VALUES (5, 'Административный директор', 13, 18, 3);
INSERT INTO `department_info2` VALUES (6, 'Проектный отдел', 4, 5, 4);
INSERT INTO `department_info2` VALUES (7, 'Технологический отдел', 6, 11, 4);
INSERT INTO `department_info2` VALUES (8, 'Финансовый отдел', 14, 15, 4);
INSERT INTO `department_info2` VALUES (9, 'Административный отдел', 16, 17, 4);
INSERT INTO `department_info2` VALUES (10, 'клиент', 7, 8, 5);
INSERT INTO `department_info2` VALUES (11, 'Сервер', 9, 10, 5);

Возможности программы

  • преимущество
    • Сводка запроса Простой эффективный
    • Нет необходимости в рекурсивных запросах, высокая производительность
  • недостаток
    • Структура относительная сложная,Уровень данных трудно понять
    • Нет Простота обслуживания Считается, что наличие левого и правого значений напрямую повлияет на последующие узлы. Поэтому, когда текущий узел будет добавлен, удален или изменен, это окажет влияние на последующую индустрию узлов;

Пример

Добавление, удаление и изменение узлов

Новый

Как показано на рисунке ниже: В разделе «Технологический отдел» в «Новом» находится отдел тестирования. Левое и правое значения, соответствующие узлу «Новый», равны 11 и 12 соответственно;

Анализ процесса:

первый шаг,все, чем11(Новыйузелиз Номер слева)большойиз Номер слевавсе +2 (фиолетовая часть)

Шаг 2,все, чем12(Новыйузелиз Правильный номер)большойиз Правильный номер Все +2 (оранжевая часть)

Шаг 3,добавить слева Правильный номер — это узлы отдела 11 и 12 соответственно.

Поскольку здесь задействовано несколько шагов, нам необходимо обеспечить атомарность операций с базой данных, поэтому требуются операции транзакций. Для удобства здесь не требуется создание хранимой процедуры, и транзакции также могут быть обеспечены в форме. кода операции; просто используйте хранимые процедуры, надежность будет выше;

Добавить хранимую процедуру узла

Язык кода:javascript
копировать
DROP PROCEDURE IF EXISTS insertDepartment;
CREATE PROCEDURE insertDepartment(IN lt_i INT,IN rt_i INT,IN lv_i INT,IN dn VARCHAR(256))
BEGIN
   DECLARE d_error INTEGER DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- Установите значение 1 в случае исключения
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- Если в таблице нет следующих данных, ему присваивается значение 2.
  
  START TRANSACTION;
  -- Добавьте +2 ко всем отделам, значение lvalue которых больше левого значения «Новый».
  UPDATE department_info2 SET lt=lt+2 WHERE lt > lt_i;
  -- Добавьте +2 ко всем отделам, ценность которых больше ценности Нового.
  UPDATE department_info2 SET rt=rt+2 WHERE rt >= lt_i;
  -- Новыйотделение Дверь  INSERT INTO department_info2(dep_name,lt,rt,lv) VALUES(dn,lt_i,rt_i,lv_i);
  SET d_error= ROW_COUNT();

    IF d_error != 1 THEN
        ROLLBACK;-- Результат не равен 1 Когда это означает, что Новый не удался и операция откатывается.
    ELSE
        COMMIT; -- Если оно равно 1, это означает, что добавление прошло успешно и отправка
    END IF;
    select d_error;
END

входные параметры

lt_i : значение нового отдела

rt_i:Новыйотделение Дверьизrvalue

lv_i:Административный Уровни отделных ворот

dn:отделение Дверьимя

Как показано на рисунке выше, вы можете вызвать хранимую процедуру Новый, чтобы добавить:

Язык кода:javascript
копировать
call insertDepartment(11, 12, 5, «Отдел испытаний»);

Исправлять

Информация об общем узле Исправлять,Здесь нечего сказать,очень Простой;

Движение узла происходит по этой схеме,большинствосложныйиз Исправлятьдействовать,Потому что весь процесс предполагает изменение позиции,Изменения уровней и другие корректировки в нескольких измерениях,Более того, должны быть обеспечены транзакционные операции;

Пример: Мы хотим изменить Технологический отдел(id = 4)передал Главный управляющий(id = 2) Непосредственно ответственный, то есть переведен в Главный управляющийпод;

Анализ процесса:

первый шаг,вычислить Технологический отделизлевый Правильный номер Разница

Шаг 2,Вычислить разницу от перемещенного родительского узла

Шаг 3,Определите, следует ли двигаться влево или вправо

Шаг 4,Вычтите (сдвиг влево)/добавьте (сдвиг вправо) разницу между этим узлом и целевым узлом.

Шаг 5,будет двигатьсяизузела такжепотомкиизузелиотецузелмеждуиз Разница минус(Сдвиг влево)/плюс(Двигайтесь вправо)

Шаг 6,Отрегулируйте уровни

Весь процесс показан на рисунке ниже, который немного сложен. Внимательно разобраться можно, объединив диаграмму и код хранимой процедуры:

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

Язык кода:javascript
копировать
DROP PROCEDURE IF EXISTS moveDepartment;
CREATE PROCEDURE moveDepartment(IN fid INT,IN tid INT)
BEGIN
   DECLARE d_error INTEGER DEFAULT 0;
  DECLARE num INTEGER DEFAULT 0; -- Удалить Разница между левым и правым значениями узла
  DECLARE mnum INTEGER DEFAULT 0; -- Разница между этапом перемещения и родительским узлом
  DECLARE ids VARCHAR(1000); -- Сохраните все наборы идентификаторов перемещения, чтобы обеспечить нормальную работу при перемещении нескольких узлов.
  DECLARE blt INT; -- Номер кому нужно переместить узел слева
  DECLARE brt INT; -- Необходимо переместить узлы Правильный номер
  DECLARE blv INT; -- Уровень, на который необходимо переместить узел
  DECLARE tlt INT; -- Номер целевого узла слева
  DECLARE trt INT; -- Правильный целевого узла номер
  DECLARE tlv INT; -- уровень целевого узла
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- Установите значение 1 в случае исключения
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- Если в таблице нет следующих данных, ему присваивается значение 2.
  
  START TRANSACTION;
  -- Запросить левый Правильный узел, который нужно переместить. номер и уровни
  SELECT lt,rt,lv INTO blt, brt,blv FROM department_info2 WHERE id = fid;
  -- Запросить левый Правильный целевой узел номер и уровни
  SELECT lt,rt,lv INTO tlt, trt,tlv FROM department_info2 WHERE id = tid;
  -- Запросите все узлы, которые необходимо определить, текущий узел и его узлы-потомки.
  SELECT GROUP_CONCAT(id) INTO ids FROM department_info2 WHERE lt>=blt AND rt <=brt;
  IF tlt > blt AND trt < brt THEN
    -- Переместитесь в свой подчиненный узел На данный момент не разрешено Если вы хотите это сделать, вам нужно настроить нижний уровень на тот же уровень. Двигайтесь снова
   SET d_error = -4;
  ELSEIF blt > tlt AND brt < trt AND blv = tlv + 1 THEN
    -- Само описание уже является целевым узлом в дочернем узле, никакой обработки не требуется, и оно выполняется напрямую.
    SET d_error = 0;
  ELSE
    -- Вычислить разницу между мобильным узлом и родительским узлом
   SET mnum = trt - brt -1;
   -- Вычислить разницу между текущим узлом и его дочерними узлами
   SET num = brt - blt + 1;
   
   -- Сначала удалите узел, прежде чем переходить из всей таблицы элементов.
   IF trt > brt THEN
     -- Двигайтесь слева направо
    UPDATE department_info2 SET lt=lt-num WHERE lt > brt AND lt < trt;
    UPDATE department_info2 SET rt=rt-num WHERE rt > brt AND rt < trt;
   ELSE
     -- Двигайтесь справа налево Измените все коэффициенты на отрицательные значения, -отрицательные числа равны +положительным числам.
    SET mnum = -mnum;
    SET num = -num;
    UPDATE department_info2 SET lt=lt-num WHERE lt >= trt AND lt < blt;
    UPDATE department_info2 SET rt=rt-num WHERE rt >= trt AND rt < blt;
   END IF;
   
   -- Отрегулируйте перемещенные узлы и подчиненные узлы
   UPDATE department_info2 SET lt=lt+mnum,rt=rt+mnum,lv = lv - (blv - tlv -1) WHERE FIND_IN_SET(id,ids);
   SET d_error= ROW_COUNT();
   
   IF d_error <= 0 THEN
     ROLLBACK;
   ELSE
     COMMIT;
   END IF;
  END IF;
    select d_error;
END

входные параметры

fid:двигатьсяизузелid

tid:Цельузелid

тест:

Язык кода:javascript
копировать
CALL moveDepartment(7,2)

удалить

Процесс удаления прямо противоположен процессу «Новый» в «Удалить». узел и собственный щелчок, больший, чем Удалить Все левые и правые значения узла нужно вычесть из Удалить Разница между левой и правой частью узла равна +1;

Как показано ниже Пример:удалить Технологический отдел

процесс:

первый шаг,вычислитьвне Удалить Разница между левой и правой частью узла равна +1;Технологический Левое и правое значения отдела равны 6 и 11 соответственно, а разница +1:11. - 6 + 1

Шаг 2,Удалить Все дочерние узлы узла машины

Шаг 3,Все узлы больше или равны Удалить узел,Вычтите разницу из всех

Также для удобства работы мы также создаем процесс хранения:

Язык кода:javascript
копировать
DROP PROCEDURE IF EXISTS removeDepartment;
CREATE PROCEDURE removeDepartment(IN did INT)
BEGIN
   DECLARE d_error INTEGER DEFAULT 0;
  DECLARE num INTEGER DEFAULT 0; -- Удалить Разница между левым и правым значениями узла
  DECLARE dlt INT; -- Удалить lзначение узла
  DECLARE drt INT; -- Удалить значение rvalue узла
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- Установите значение 1 в случае исключения
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- Если в таблице нет следующих данных, ему присваивается значение 2.
  
  START TRANSACTION;
  -- Запрос Удалить Левое и правое значения узла
  SELECT lt,rt INTO dlt, drt FROM department_info2 WHERE id = did;
  -- Вычислить разницу между текущим узлом и его дочерними узлами
  SET num = drt - dlt + 1;
  
  -- удалить текущий узел и его дочерние узлы
  DELETE FROM department_info2 WHERE lt>=dlt AND rt<=drt;
  SET d_error= ROW_COUNT();
  
  -- Левые и правые значения уменьшают соответствующую интерполяцию
  UPDATE department_info2 SET lt=lt-num WHERE lt > dlt;
    UPDATE department_info2 SET rt=rt-num WHERE rt > drt;
  
    IF d_error != num/2 THEN -- Определить, соответствует ли количество удалений количеству текущих узлов + узлов-потомков, если нет, выполнить откат напрямую;
        ROLLBACK;
    ELSE
        COMMIT;
    END IF;
    select d_error;
END

тест:

Язык кода:javascript
копировать
-- Проектный отделизid = 7
SET @id=7;
call removeDepartment(@id);
Есть ли дочерние узлы (листовые узлы)

Никаких дополнительных запросов не требуется,Прямо через левую часть узла Правильный номер,Ты можешь судить это лист?узел Понятно;когдаПравильный номер - Номер слева = 1час,иллюстрироватьтекущий узел Он принадлежит листьямузел,В противном случае это не так;

Опросить все подчиненные отделы

Эквивалент: номер запроса left больше текущего узла, Правильный номер Сравниватьтекущий узел Маленький,и слой Сравниватьтекущий узелбольшой1извсе узлы;

пример:Запрос Отдел продукции(lt = 3,rt = 12,lv = 3) Все подчиненные подразделения:

Язык кода:javascript
копировать
SET @lt = 3;  -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SET @lv = 3;  -- Предыдущий уровень
SELECT * from department_info2 where lt > @lt AND rt < @rt AND `lv` = @lv + 1
Запросить все подчиненные отделы (внучатые узлы)

Это требование редко встречается в реальном бизнесе и используется только для демонстрации осуществимости;

Запрос узлов-внучат аналогичен запросу дочерних узлов, за исключением того, что уровень меняется с +1 на +2.

пример:Запрос Отдел продукции(lt = 3,rt = 12,lv = 3) Все подчиненные подразделения:

Язык кода:javascript
копировать
SET @lt = 3;  -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SET @lv = 3;  -- Предыдущий уровень
SELECT * from department_info2 where lt > @lt AND rt < @rt AND `lv` = @lv + 2;
Поиск по всем подчиненным отделам

Эквивалентно:Сравниватьтекущий узел Номер слевабольшой,Правильный номер Все малые узлы являются узлами-потомками;

пример,Запрос Отдел продукции(lt = 3,rt = 12) Все подчиненные подразделения:

Язык кода:javascript
копировать
SET @lt = 3;  -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SELECT * from department_info2 where lt > @lt AND rt < @rt;
Запросите отдел, к которому вы принадлежите

Эквивалент: Номер слева Сравнивать Собственный Маленький,Правильный номер Сравнивать Собственныйбольшой,И узел с уровнем -1,То есть родительский узел

пример,Запрос Технологический отдел(lt = 6, rt = 11) Дочерние подразделения:

Язык кода:javascript
копировать
SET @lt = 6;  -- узел Номер слева
SET @rt = 11; -- узел Правильный номер
SET @lv = 4;  -- Предыдущий уровень
SELECT * from department_info2 where lt < @lt AND rt > @rt AND `lv` = @lv - 1 ;
Проверьте все вышестоящие отделы

и Запрос родительского узел аналогичен, за исключением того, что уровень проверки All Number больше не нужен. слева Сравнивать Собственный Маленький,Правильный номер больше вас — это ваши собственные родительские узлы;

пример,Запрос Отдел продукции(lt = 3,rt = 12) Все вышестоящие отделы

Язык кода:javascript
копировать
SET @lt = 3;  -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SELECT * from department_info2 where lt < @lt AND rt > @rt;
Посчитайте количество всех подчиненных отделов

Чтобы подсчитать количество узлов-потомков, дополнительный запрос не требуется. Вы можете рассчитать количество дочерних узлов на основе собственных чисел слева и справа;

вычислитьчиновник:(Правильный номер - Номер слева - 1 )/ 2

пример,вычислить Отдел продукции(id = 4) Количество подведомственных подразделений:

Язык кода:javascript
копировать
SELECT dep_name,(rt - lt - 1) / 2 as num FROM department_info2 where id = 4

3Форматирование данных

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

основные объекты

Язык кода:javascript
копировать
@Data
@RequiredArgsConstructor
public class LrItem {

    @NonNull
    private Integer id;

    @NonNull
    private String depName;

    @NonNull
    private Integer left;

    @NonNull
    private Integer right;

    private Integer level;

    private Integer parentId;

    /**
     * Это лист?
     */
    private Boolean isLeaf;

    private List<LrItem> childItem;
}

данные испытаний

Это всего лишь демонстрация форматированных данных, и в реальных бизнес-сценариях это не так уж и сложно; этот пакет данных обычно запрашивается из базы данных.

Язык кода:javascript
копировать
List<LrItem> deps = new ArrayList<>();
deps.add(new LrItem(1, "совет директоров", 1, 22));
deps.add(new LrItem(2, "Главный управляющий", 2, 19));
deps.add(new LrItem(3,"совет директоровсекретарь", 20, 21));
deps.add(new LrItem(4, "Отдел продукции", 3, 12));
deps.add(new LrItem(5, "Административный директор", 13, 18));
deps.add(new LrItem(6, "Проектный отдел", 4, 5));
deps.add(new LrItem(7, "Технологический отдел", 6, 11));
deps.add(new LrItem(8, "Финансовый отдел", 14, 15));
deps.add(new LrItem(9, "Административный отдел", 16, 17));
deps.add(new LrItem(10, "клиент", 7, 8));
deps.add(new LrItem(11, "Сервер", 9, 10));

Организация данных

Язык кода:javascript
копировать
public static void init(List<LrItem> deps) {
    // Если база данных отсортирована  Здесь не нужно сортировать
    deps.sort(Comparator.comparing(LrItem::getLeft));

    // для уровня расчета Кэшируйте значение в правой части узла.
    List<Integer> rights = new ArrayList<>();
    Map<Integer, Integer> mp = new HashMap<>();

    // Инициализируйте уровень узлов, конечные узлы а также Идентификатор родительского узла Соответствующие данные
    deps.forEach(item -> {
        if (rights.size() > 0) {
            // Как только обнаруживается, что значение в правой части этого узла больше предыдущего, это означает, что уровень переместился вверх. Необходимо удалить предыдущий базовый актив и стоимость
            // В большинстве случаев здесь возможен только случай удаления предыдущего значения.
            while (rights.get(rights.size() - 1) < item.getRight()) {
                rights.remove(rights.size() - 1);//Удалить с конца права
            }
        }
        Integer _level = rights.size() + 1;
        item.setLevel(_level);
        mp.put(_level, item.getId());
        item.setParentId(mp.containsKey(_level - 1) ? mp.get(_level - 1) : 0); //Рассчитываем номер вышестоящего отдела
        item.setIsLeaf(item.getLeft() == item.getRight() - 1);//Определяем, является ли это листовым отделом
        rights.add(item.getRight());
    });

    System.out.println(rights);
    System.out.println(mp);
}

Рекурсивная сортировка

Идея рекурсии относительно проста и понятна: после получения текущего узла найти среди всех узлов свои собственные дочерние узлы. Когда все узлы найдены, весь процесс древовидной структуры завершен;

Мы можем объединить новые возможности Java 8 Stream, чтобы сделать весь рекурсивный код относительно простым и понятным;

Язык кода:javascript
копировать
/**
 * @param deps Все данные
 */
public static void recursive(List<LrItem> deps) {
    init(deps);
    //Получаем родительский узел
    List<LrItem> collect = deps.stream()
            .filter(m -> m.getParentId() == 0)
            .map(m ->
                    {
                        m.setChildItem(getChildrens(m, deps));
                        return m;
                    }
            ).collect(Collectors.toList());

    // По общему запросу будет только один корневой узел, поэтому здесь выносится первый элемент. Если их больше одного, его можно настроить в соответствии с потребностями. Он используется только для тестирования.
    System.out.println(JSON.toJSON(collect.get(0)));
}

/**
 * Рекурсивный Запрос дочерних узлов
 *
 * @param root корневой узел
 * @param all  все узлы
 * @return корневой информация об узле
 */
private static List<LrItem> getChildrens(LrItem root, List<LrItem> all) {
    List<LrItem> children = all.stream()
            .filter(m -> Objects.equals(m.getParentId(), root.getId()))
            .map(m -> {
                        m.setChildItem(getChildrens(m, all));
                        return m;
                    }
            ).collect(Collectors.toList());
    return children;
}

Нерекурсивная сортировка в обратном порядке

этообмен пространства на времяизплан;

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

  • первый шаг,вычислитьвне层级а также Идентификатор родительского узла
  • Шаг 2, сортировка по уровню
  • Шаг 3. Пройдите корневой узел от самого глубокого узла в обратном порядке. Пройдите процесс доMap<Integer, List<LrItem>>изкэширование путиIDитекущий узелизданные,После перехода на уровень выше,Просто уберите сценку обо мне, сохраненную на карте.,保存到Собственный对象изchildItemПолесередина
Язык кода:javascript
копировать
public static void reverseFormat(List<LrItem> deps) {
    init(deps);

    deps.sort(Comparator.comparing(LrItem::getLevel));
    deps.forEach(item -> System.out.println(JSON.toJSONString(item)));

    // Временно кэшируйте контейнеры для соответствующих узлов.
    Map<Integer, List<LrItem>> childCache = new HashMap<>();

    // текущий узел
    LrItem lrItem = null;
    //int level = 0;
    // Используйте обход в обратном порядке, чтобы организовать сбор каждого дочернего узла.
    for (int i = deps.size() - 1; i >= 0; i--) {
        lrItem = deps.get(i);
        Integer parentId = lrItem.getParentId();
        if (null == lrItem || null == parentId) {
            continue;
        }

        List<LrItem> childItems = childCache.get(parentId);
        if (null == childItems) {
            childCache.put(parentId, childItems = new ArrayList<>());
        }
        childItems.add(lrItem);

        // Если это не листовой узел, это означает, что у него должны быть дочерние узлы. Найдите его в кеше и заполните его.
        if (!lrItem.getIsLeaf()) {
            childItems = childCache.get(lrItem.getId());
            childItems.sort(Comparator.comparing(LrItem::getId));
            lrItem.setChildItem(childItems);
            childCache.remove(lrItem.getId());
        }
    }

    System.out.println(JSON.toJSONString(lrItem));
}

Форматированные данные

В любом случае вы получите следующие структурированные данные:

Язык кода:javascript
копировать
{
    "depName": "совет директоров",
    "id": 1,
    "isLeaf": false,
    "left": 1,
    "level": 1,
    "prientId": 0,
    "right": 22,
    "childItem": [
        {
            "depName": "Главный управляющий",
            "id": 2,
            "isLeaf": false,
            "left": 2,
            "level": 2,
            "prientId": 1,
            "right": 19,
            "childItem": [
                {
                    "depName": "Административный директор",
                    "id": 5,
                    "isLeaf": false,
                    "left": 13,
                    "level": 3,
                    "prientId": 2,
                    "right": 18,
                    "childItem": [
                        {
                            "depName": "Проектный отдел",
                            "id": 6,
                            "isLeaf": true,
                            "left": 4,
                            "level": 4,
                            "prientId": 4,
                            "right": 5
                        },
                        {
                            "depName": "Технологический отдел",
                            "id": 7,
                            "isLeaf": false,
                            "left": 6,
                            "level": 4,
                            "prientId": 4,
                            "right": 11,
                            "childItem": [
                                {
                                    "depName": "клиент",
                                    "id": 10,
                                    "isLeaf": true,
                                    "left": 7,
                                    "level": 5,
                                    "prientId": 7,
                                    "right": 8
                                },
                                {
                                    "depName": "Сервер",
                                    "id": 11,
                                    "isLeaf": true,
                                    "left": 9,
                                    "level": 5,
                                    "prientId": 7,
                                    "right": 10
                                }
                            ]
                        }
                    ],
                    "depName": "Отдел продукции",
                    "id": 4,
                    "isLeaf": false,
                    "left": 3,
                    "level": 3,
                    "prientId": 2,
                    "right": 12
                },
                {
                    "childItem": [
                        {
                            "depName": "Финансовый отдел",
                            "id": 8,
                            "isLeaf": true,
                            "left": 14,
                            "level": 4,
                            "prientId": 5,
                            "right": 15
                        },
                        {
                            "depName": "Административный отдел",
                            "id": 9,
                            "isLeaf": true,
                            "left": 16,
                            "level": 4,
                            "prientId": 5,
                            "right": 17
                        }
                    ]
                }
            ]
        },
        {
            "depName":"совет директоровсекретарь",            "id": 3,
            "isLeaf": true,
            "left": 20,
            "level": 2,
            "prientId": 1,
            "right": 21
        }
    ]
}

4 сравнения

Учитывая приведенное выше подробное объяснение, давайте сравним их более интуитивно в виде таблицы:

Функция

План отношений отца и сына

предординальная схема

Новый

Простой

сложный

Исправлять

Простой

сложный

удалить

сложный

сложный

Определить листовые узлы

сложный (если это не усложняет редактирование, организуйте его заранее)

Простой

Запрос дочерних узлов

Простой

Простой

Запросить дочерний узел

сложный

Простой

Запрос родительского узла

Простой

Простой

Узлы-предки запроса

сложный

Простой

Подсчитайте количество узлов-потомков

сложный

Простой

Применимые сценарии

структура Простой,Мало уровней,Немного статистики,частые изменения

структурасложный,Немного изменений,глубокий уровень,Нужна сложная статистика

5 Резюме

Проанализировав общие сценарии двух решений,,Выяснилось, что у каждого есть свои преимущества и недостатки.,Так что простите меня, если заголовок по сравнению с этим немного похож на заголовок;,Лично я по-прежнему предпочитаю улучшенную предординационную схему.

План отношений отца и сына Подходящийструктураотносительно Простой、Мало уровнейизнуждаться;

и предординационная схема就更Подходящийструктурасложный,Немного изменений,глубокий уровень,Существует потребность в частой сводной статистике;

Так,Все та же фраза,Не существует абсолютно хорошего плана, есть только неподходящие сценарии;需要更具Собственный业务изреальная ситуация,Будьте осмотрительны, чтобы выбрать вариант, который наиболее выгоден для проекта.

Хорошо, на сегодня это все; если у вас есть какие-либо вопросы, не стесняйтесь общаться и вносить исправления!

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose