Привет всем, я Иханг;
Независимо от того, занимаетесь ли вы фронтенд- или бэкенд-разработкой,Тогда я считаю, что необходимость в древовидной структуре наверняка уже встречалась раньше.,Особенно проекты типа платформы управления,Обычно это строка меню с древовидной структурой.,Другой пример,Организационная структура компании,иерархические отношения、Отношения принадлежностиОжидание спроса,По сути, они являются проявлением древовидной структуры;
удовлетворить эту потребность,Самая распространенная и простая идея дизайна::отношения отца и сына,Дочерний элемент сохраняет свой родительский идентификатор через поле,Затем получите иерархические отношения посредством рекурсии;
Несколько дней назад некоторые друзья из группы технического обмена спросили: В реальном бизнесе в древовидной структуре слишком много данных, а уровни глубокие. После того как процесс запроса является рекурсивным, производительность относительно низкая. Есть ли что-нибудь. лучше? Идеи дизайна делают запросы и статистику более удобными и эффективными;
Сегодня я представлю вам новое решение для проектирования древовидной структуры.:Улучшен метод дерева предзаказа,Преимущества в запросах и статистике,Рекурсивное проектное решение намного шире, чем отношения родитель-потомок;
В этой статье будут подробно объяснены и сравнены методы реализации, преимущества и недостатки двух решений.
Каталог статей:
Что касается спроса на древовидные структуры, независимо от того, какой метод используется, проблемы, которые необходимо решить, одни и те же. Вот общие проблемы древовидных структур:
В ответ на вышеперечисленные проблемы воспользуемся Организационной системой Простой. структура компании Пример,Давайте посмотрим,Как реализуются и решаются оба решения?
Вся информация в этой статье реализована с использованием MySQL+Java.,Основные идеи схожи,фактическое использование,Может быть основано на особенностях языкаа также Просто настройте его так, как вы привыкли。
Отношения «родитель-потомок», как следует из названия, означают, что текущий узел обращает внимание только на то, кем является его родительский узел, и сохраняет его. Чтобы узнать, какие дочерние узлы у меня есть, мне нужно только глобально найти все элементы, чей родительский идентификатор согласован. с моим удостоверением личности;
Как показано ниже:
Согласно приведенной выше схеме Пример, соответствующая структура таблицы выглядит следующим образом:
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-скрипт:
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);
Поскольку запрос родительских и дочерних узлов требует рекурсии, для удобства запроса здесь созданы две функции:
Функция для рекурсивного запроса идентификаторов узлов-потомков
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 ;
тест: Опросить все дочерние узлы в разделе «Технологический отдел»?
SELECT queryChildrenDepInfo(4);
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(4));
Функция для рекурсивного запроса идентификаторов узлов-предков
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 ;
тест: Опросить все родительские узлы Технологического отдела?
SELECT queryParentDepInfo(7);
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(7));
Добавить узел
Например, вы хотите добавить отдел тестирования в раздел «Технологический отдел».
INSERT INTO department_info(`id`, `dep_name`, `level`, `parent_id`) VALUES (12, «Отдел испытаний», 5, 7);
Изменить узел
Например: изменить отдел тестирования (ID = 12) Поднимите и положите в Отдел. продукции(ID = 4) Далее вам нужно только изменить ID родительского узла, соответствующего тестовому отделу, на 4.
SET @id = 12;
SET @pid = 4;
UPDATE department_info SET `parent_id` = @pid WHERE `id` = @id;
Удалить узел
По сравнению с добавлением и изменением удаление является немного особенным. Если у удаленного узла есть дочерние узлы, это означает, что дочерние узлы также необходимо удалить одновременно;
Поэтому вам необходимо использовать функцию для рекурсивного запроса идентификаторов узлов-потомков (queryChildrenDepInfo), созданную выше.
например:удалить Технологический отдел Дверь;
DELETE FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(7));
В рамках этой схемы существует два способа определить, является ли это листовым узлом:
Подсчитайте количество текущих узлов и узлов-потомков.
Рекурсивно запрашивать идентификаторы всех дочерних узлов.,и посчитаем количество,Поскольку запрос функции содержит сам узел,Итак, здесь использованоCOUNT(*)-1
подсчитать количество дочерних узлов,Если равно 0, это листовой узел.,Если оно больше 0, это означает, что это не листовой узел;
-- Проверять Проектный Является ли отдел (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) дочерние узлы
SET @id = 4;
SET @lv = 3;
SELECT * from department_info where parent_id = @id AND `level` = @lv + 1;
В реальных бизнес-сценариях это требование встречается редко. Оно в основном используется для демонстрации работоспособности и не исключает его использования в особых случаях.
Запросить дочерний Узел гораздо более проблематичен, чем дочерние узлы, поскольку между текущим узлом и дочерним узлом нет связи данных, поэтому вам нужно использовать дочерние узлы для поиска внучатого узла, поэтому вы должны использовать функцию здесь. для рекурсивного запроса идентификаторов узлов-потомков Понятно;
пример:Запрос Отдел продукции(id = 4,level = 3) узел-внук
-- Запросить дочерний узел
SET @id = 4;
SET @lv = 3;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id)) AND `level` = @lv + 2;
Запрос подчиненных отделов аналогичен запросу подчиненных отделов (дочерних узлов), за исключением того, что нет необходимости проверять иерархию;
Пример: Запросить все подчиненные отделы в разделе «Отдел продукции»?
SET @id = 4;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));
То есть для запроса родительского узла; в соответствии с этим решением идентификатор родительского узла сохранен в узле, и родительский узел можно получить непосредственно через идентификатор.
Поскольку текущий узел сохраняет только идентификатор родительского узла, информацию более высокого уровня можно получить только уровень за уровнем посредством рекурсии;
пример:Запрос Технологический отдел(id = 7) Все вышестоящие отделы;
SET @id = 7;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(@id));
Это то же самое, что запросить, является ли это листовым узлом, но способ интерпретации полученных данных другой;
Пример: Посчитайте количество подчиненных подразделений Технологического отдела?
SET @id = 7;
SELECT COUNT(*)-1 FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));
Из приведенной выше схемы отношений родитель-потомок видно, что большинство операций требуют рекурсивного запроса всех узлов-потомков. Если в начале статьи упомянуто много уровней и глубин, рекурсивный процесс сильно повлияет на статистическую производительность запроса. ;
Далее давайте представим улучшенную схему древовидной структуры дерева предварительного заказа.,Узел больше не сохраняет идентификатор родительского узла.,СкорееДобавьте 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-заявление:
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 соответственно.
Поскольку здесь задействовано несколько шагов, нам необходимо обеспечить атомарность операций с базой данных, поэтому требуются операции транзакций. Для удобства здесь не требуется создание хранимой процедуры, и транзакции также могут быть обеспечены в форме. кода операции; просто используйте хранимые процедуры, надежность будет выше;
Добавить хранимую процедуру узла:
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:отделение Дверьимя
Как показано на рисунке выше, вы можете вызвать хранимую процедуру Новый, чтобы добавить:
call insertDepartment(11, 12, 5, «Отдел испытаний»);
Исправлять
Информация об общем узле Исправлять,Здесь нечего сказать,очень Простой;
Движение узла происходит по этой схеме,большинствосложныйиз Исправлятьдействовать,Потому что весь процесс предполагает изменение позиции,Изменения уровней и другие корректировки в нескольких измерениях,Более того, должны быть обеспечены транзакционные операции;
Пример: Мы хотим изменить Технологический отдел(id = 4)передал Главный управляющий(id = 2) Непосредственно ответственный, то есть переведен в Главный управляющийпод;
Анализ процесса:
первый шаг,вычислить Технологический отделизлевый Правильный номер Разница
Шаг 2,Вычислить разницу от перемещенного родительского узла
Шаг 3,Определите, следует ли двигаться влево или вправо
Шаг 4,Вычтите (сдвиг влево)/добавьте (сдвиг вправо) разницу между этим узлом и целевым узлом.
Шаг 5,будет двигатьсяизузела такжепотомкиизузелиотецузелмеждуиз Разница минус(Сдвиг влево)/плюс(Двигайтесь вправо)
Шаг 6,Отрегулируйте уровни
Весь процесс показан на рисунке ниже, который немного сложен. Внимательно разобраться можно, объединив диаграмму и код хранимой процедуры:
Чтобы облегчить работу и избежать ошибок, основная логика также реализована в виде хранимых процедур, чтобы снизить риск ошибок:
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
тест:
CALL moveDepartment(7,2)
удалить
Процесс удаления прямо противоположен процессу «Новый» в «Удалить». узел и собственный щелчок, больший, чем Удалить Все левые и правые значения узла нужно вычесть из Удалить Разница между левой и правой частью узла равна +1;
Как показано ниже Пример:удалить Технологический отдел
процесс:
первый шаг,вычислитьвне Удалить Разница между левой и правой частью узла равна +1;Технологический Левое и правое значения отдела равны 6 и 11 соответственно, а разница +1:11. - 6 + 1
Шаг 2,Удалить Все дочерние узлы узла машины
Шаг 3,Все узлы больше или равны Удалить узел,Вычтите разницу из всех
Также для удобства работы мы также создаем процесс хранения:
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
тест:
-- Проектный отделизid = 7
SET @id=7;
call removeDepartment(@id);
Никаких дополнительных запросов не требуется,Прямо через левую часть узла Правильный номер,Ты можешь судить это лист?узел Понятно;когдаПравильный номер - Номер слева = 1час,иллюстрироватьтекущий узел Он принадлежит листьямузел,В противном случае это не так;
Эквивалент: номер запроса left больше текущего узла, Правильный номер Сравниватьтекущий узел Маленький,и слой Сравниватьтекущий узелбольшой1извсе узлы;
пример:Запрос Отдел продукции(lt = 3,rt = 12,lv = 3) Все подчиненные подразделения:
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) Все подчиненные подразделения:
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) Все подчиненные подразделения:
SET @lt = 3; -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SELECT * from department_info2 where lt > @lt AND rt < @rt;
Эквивалент: Номер слева Сравнивать Собственный Маленький,Правильный номер Сравнивать Собственныйбольшой,И узел с уровнем -1,То есть родительский узел
пример,Запрос Технологический отдел(lt = 6, rt = 11) Дочерние подразделения:
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) Все вышестоящие отделы
SET @lt = 3; -- узел Номер слева
SET @rt = 12; -- узел Правильный номер
SELECT * from department_info2 where lt < @lt AND rt > @rt;
Чтобы подсчитать количество узлов-потомков, дополнительный запрос не требуется. Вы можете рассчитать количество дочерних узлов на основе собственных чисел слева и справа;
вычислитьчиновник:(Правильный номер - Номер слева - 1 )/ 2;
пример,вычислить Отдел продукции(id = 4) Количество подведомственных подразделений:
SELECT dep_name,(rt - lt - 1) / 2 as num FROM department_info2 where id = 4
Независимо от решения, уровень данных является плоским, а структурированные отношения выражаются только через логику полей. Как после запроса структурировать данные в древовидную структуру и отобразить ее? Ниже представлены два рекурсивных и нерекурсивных метода. реализовать:
основные объекты
@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;
}
данные испытаний
Это всего лишь демонстрация форматированных данных, и в реальных бизнес-сценариях это не так уж и сложно; этот пакет данных обычно запрашивается из базы данных.
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));
Организация данных
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, чтобы сделать весь рекурсивный код относительно простым и понятным;
/**
* @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.
Map<Integer, List<LrItem>>
изкэширование путиIDитекущий узелизданные,После перехода на уровень выше,Просто уберите сценку обо мне, сохраненную на карте.,保存到Собственный对象изchildItem
Полесередина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));
}
В любом случае вы получите следующие структурированные данные:
{
"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
}
]
}
Учитывая приведенное выше подробное объяснение, давайте сравним их более интуитивно в виде таблицы:
Функция | План отношений отца и сына | предординальная схема |
---|---|---|
Новый | Простой | сложный |
Исправлять | Простой | сложный |
удалить | сложный | сложный |
Определить листовые узлы | сложный (если это не усложняет редактирование, организуйте его заранее) | Простой |
Запрос дочерних узлов | Простой | Простой |
Запросить дочерний узел | сложный | Простой |
Запрос родительского узла | Простой | Простой |
Узлы-предки запроса | сложный | Простой |
Подсчитайте количество узлов-потомков | сложный | Простой |
Применимые сценарии | структура Простой,Мало уровней,Немного статистики,частые изменения | структурасложный,Немного изменений,глубокий уровень,Нужна сложная статистика |
Проанализировав общие сценарии двух решений,,Выяснилось, что у каждого есть свои преимущества и недостатки.,Так что простите меня, если заголовок по сравнению с этим немного похож на заголовок;,Лично я по-прежнему предпочитаю улучшенную предординационную схему.
План отношений отца и сына Подходящийструктураотносительно Простой、Мало уровнейизнуждаться;
и предординационная схема就更Подходящийструктурасложный,Немного изменений,глубокий уровень,Существует потребность в частой сводной статистике;
Так,Все та же фраза,Не существует абсолютно хорошего плана, есть только неподходящие сценарии;需要更具Собственный业务изреальная ситуация,Будьте осмотрительны, чтобы выбрать вариант, который наиболее выгоден для проекта.
Хорошо, на сегодня это все; если у вас есть какие-либо вопросы, не стесняйтесь общаться и вносить исправления!