#mysql
#mysql
Вопрос:
У меня есть таблица MySQL, которая выглядит примерно так:
----------------------
| ID | Name | Parent |
----------------------
| 1 | a | null |
| 2 | b | null |
| 3 | c | 1 |
| 4 | d | 3 |
| 5 | e | 2 |
| 6 | f | 2 |
----------------------
с неизвестным числом возможной глубины отношения родитель / потомок.
Я хочу запрос, который даст мне следующий результат:
-----------------
| ID | Children |
-----------------
| 1 | 3,4 | -- because 4 is a child of 3, and 3 is a child of 1, it should show in both
| 2 | 5,6 |
| 3 | 4 |
| 4 | null |
| 5 | null |
| 6 | null |
-----------------
Возможно ли получить такой результат в запросе? Я пробовал все, что я знаю, чтобы попробовать, и искал везде и не нашел ничего, что дало бы мне этот результат.
Комментарии:
1. Какова ваша версия MySQL?
2. Версия MySQL 5.7
3. @forpas это часть моей проблемы. Я нашел решение, которое работает в MySQL 8.0, но я использую MariaDB, который в настоящее время основан на версии 5.7
4. @Randy вы можете использовать
FIND_IN_SET
для этой цели5. @aRvi Хотите уточнить? Я не уверен, как эта конкретная функция поможет мне в этом случае.
Ответ №1:
Рассматривая таблицу как employees
с полями ID, Name, Parent
.
Подход 1: Когда мы знаем глубину нашей иерархии, мы можем просто объединить наше табличное n
время, равное нашей иерархии, и можем использовать GROUP BY
для получения желаемых результатов. Здесь это 3 так
SELECT t1.ID AS lev1, GROUP_CONCAT(CONCAT_WS(',', t2.ID, t3.ID)) AS childs
FROM employees AS t1
LEFT JOIN employees AS t2 ON t2.Parent = t1.ID
LEFT JOIN employees AS t3 ON t3.Parent = t2.ID
GROUP BY t1.ID
Соответствующую скрипку вы можете посмотреть здесь.
Подход 2: когда мы не знаем глубину нашей иерархии
Для этого мы создадим две процедуры.
store_emp_childs
— Он сохранит родительский элемент и его дочерний элемент во временной таблице.get_emp_child
— Он создаст временную таблицу, вызоветstore_emp_childs
для генерации результирующего набора. Выберите (верните) данные из временной таблицы и удалите временную таблицу.
CREATE DEFINER=`root`@`localhost` PROCEDURE `get_emp_child`()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
CREATE TEMPORARY TABLE `tmp_emp_child` (
`emp_id` INT(11) NOT NULL,
`child_id` INT(11) NOT NULL,
PRIMARY KEY (`emp_id`, `child_id`)
);
CALL store_emp_childs(NULL, '');
SELECT e.ID, GROUP_CONCAT(ec.child_id) AS childs
FROM employees e
LEFT JOIN tmp_emp_child ec ON e.ID = ec.emp_id
GROUP BY e.ID;
DROP TEMPORARY TABLE tmp_emp_child;
END
CREATE DEFINER=`root`@`localhost` PROCEDURE `store_emp_childs`(
IN `int_parent` INT,
IN `old_parents` VARCHAR(100)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE finished, current_id INTEGER DEFAULT 0;
DEClARE cur CURSOR FOR SELECT id FROM employees WHERE IFNULL(Parent, 0) = IFNULL(int_parent, 0);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN cur;
curID: LOOP
FETCH cur INTO current_id;
IF finished = 1 THEN
LEAVE curID;
END IF;
INSERT INTO tmp_emp_child (emp_id, child_id)
SELECT id, current_id FROM employees WHERE FIND_IN_SET(id, old_parents) OR id = int_parent
ON DUPLICATE KEY UPDATE emp_id = emp_id;
CALL store_emp_childs(current_id, CONCAT(old_parents, ',', current_id));
END LOOP curID;
CLOSE cur;
END
Примечание:
- Мы рекурсивно вызываем наш
store_emp_childs
. Для этого требуетсяmax_sp_recursion_depth
, чтобы параметр был установлен больше 0. Я рекомендую сделать это 250. Это не сработает, если записи больше, чем эта глубина рекурсии. Будем смотреть дальше, чтобы улучшить это. - Мы создаем временную таблицу, поэтому у пользователя должны быть права на ее создание.
Комментарии:
1. Очень хорошая статья, если вы хотите увидеть новый способ хранения такой информации и ее извлечения. Управление иерархическими данными в MySQL
2. Большое вам спасибо! Это сработало с 1 небольшим изменением… Мне просто нужно было изменить разделитель, затем поставить новый разделитель после каждого
END
оператора, а затем изменить разделитель обратно.