Как создать MySQL иерархический рекурсивный запрос


272

У меня есть таблица MySQL, которая выглядит следующим образом:

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

Теперь я хочу иметь один запрос MySQL, для которого я просто предоставляю идентификатор [например, скажем 'id = 19'], тогда я должен получить все его дочерние идентификаторы [т.е. результат должен иметь идентификаторы '20, 21,22 ']. ... Кроме того, иерархия детей неизвестна, она может варьироваться ....

Кроме того, у меня уже есть решение с использованием цикла for ..... Дайте мне знать, как добиться того же, используя один запрос MySQL, если это возможно.


Предположим, что иерархия имеет 7 уровней. Как вы думаете, как будет выглядеть выходная таблица?
Джонатан Леффлер

1
MySQL (все еще) не поддерживает иерархические запросы (как другие современные СУБД). Вам нужно будет написать хранимую процедуру или использовать другую модель данных.
a_horse_with_no_name


1
MYSQL 8.0 будет поддерживать рекурсивный запрос с использованием CTE (Common Table Expressions)
user3712320

Как насчет получения полного списка сообщений, начиная с последнего идентификатора комментария? Или последний ребенок?
Джо

Ответы:


394

Для MySQL 8+: используйте рекурсивный withсинтаксис.
Для MySQL 5.x: используйте встроенные переменные, идентификаторы пути или самостоятельные соединения.

MySQL 8+

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

Значение, указанное в, parent_id = 19должно быть установлено на idродительского элемента, для которого вы хотите выбрать всех потомков.

MySQL 5.x

Для версий MySQL, которые не поддерживают Common Table Expressions (до версии 5.7), этого можно достичь с помощью следующего запроса:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv)
and     length(@pv := concat(@pv, ',', id))

Вот скрипка .

Здесь значение, указанное в, @pv := '19'должно быть установлено на idродительского элемента, для которого вы хотите выбрать всех потомков.

Это также будет работать, если у родителя несколько детей. Однако требуется, чтобы каждая запись удовлетворяла условию parent_id < id, иначе результаты не будут полными.

Переменные в запросе

Этот запрос использует определенный синтаксис MySQL: переменные назначаются и изменяются во время его выполнения. Некоторые предположения сделаны относительно порядка исполнения:

  • Предложение fromоценивается первым. Так вот где @pvинициализируется.
  • Предложение whereоценивается для каждой записи в порядке извлечения из fromпсевдонимов. Таким образом, именно здесь ставится условие включения только тех записей, для которых родительский объект уже был идентифицирован как находящийся в дереве потомков (все потомки первичного родителя постепенно добавляются @pv).
  • Условия в этом whereпункте оцениваются по порядку, и оценка прерывается, как только общий результат определен. Поэтому второе условие должно быть на втором месте, так как оно добавляет idк родительскому списку, и это должно произойти, только если idпроходит первое условие. lengthФункция вызывается только , чтобы убедиться , что это условие всегда истинно, даже если pvстрока будет по какой - то причине дают falsy значение.

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

вы можете получить ожидаемые результаты, но это не гарантируется [...] порядок вычисления для выражений с участием пользовательских переменных не определен.

Таким образом, даже несмотря на то, что он работает в соответствии с вышеуказанным запросом, порядок оценки может все еще изменяться, например, когда вы добавляете условия или используете этот запрос в качестве представления или подзапроса в большем запросе. Это «особенность», которая будет удалена в будущем выпуске MySQL :

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

Как указано выше, начиная с MySQL 8.0, вы должны использовать рекурсивный withсинтаксис.

КПД

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

Альтернатива 1: with recursive,connect by

Все больше и больше баз данных реализуют стандартный WITH [RECURSIVE]синтаксис SQL: 1999 ISO для рекурсивных запросов (например, Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). Начиная с версии 8.0, MySQL также поддерживает это . Смотрите верхнюю часть этого ответа для синтаксиса, чтобы использовать.

Некоторые базы данных имеют альтернативный нестандартный синтаксис для иерархического поиска, такой как CONNECT BYпредложение, доступное в Oracle , DB2 , Informix , CUBRID и других базах данных.

MySQL версии 5.7 не предлагает такую ​​функцию. Когда ваш движок базы данных предоставляет этот синтаксис или вы можете перейти на тот, который это делает, тогда это, безусловно, лучший вариант. Если нет, то также рассмотрите следующие альтернативы.

Альтернатива 2: Идентификаторы стиля пути

Все станет намного проще, если вы назначите idзначения, которые содержат иерархическую информацию: путь. Например, в вашем случае это может выглядеть так:

ID       | NAME
19       | category1   
19/1     | category2  
19/1/1   | category3  
19/1/1/1 | category4  

Тогда ваш selectбудет выглядеть так:

select  id,
        name 
from    products
where   id like '19/%'

Альтернатива 3: повторное самостоятельное соединение

Если вы знаете верхний предел того, насколько глубоким может стать ваше дерево иерархии, вы можете использовать стандартный sqlзапрос, подобный следующему:

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

Смотрите эту скрипку

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


26
Мне нравится ваше объяснение. Он не просто дает ответ, он объясняет, почему он решает проблему, поэтому мы можем извлечь из этого уроки. РЕДАКТИРОВАТЬ: также здорово, что он не зависит от заранее знать количество уровней.
Бизон,

1
@ Зубр, я очень ценю твою обратную связь. Спасибо!
Trincot

2
@ Avión, это не то, что вы должны где-то поместить, это требование, чтобы для всех записей это условие выполнялось. Если у вас есть одна или несколько записей, где parent_id > idвы не можете использовать это решение.
Trincot

2
Для тех, кто хочет использовать этот WITH RECURSIVEметод, я нашел следующую статью действительно полезной с различными сценариями, такими как глубина рекурсии, различия, циклы обнаружения и закрытия
Horse

2
Я попробовал основное решение на MySQL5.7 на своем компьютере, на своих собственных таблицах, но это не сработало из-за эквивалента выражения @pv: = concat (@pv, ',', id), равного false. Я исправил это, изменив его на длину (@pv: = concat (@pv, ',', id))> 0, так что это всегда верно.
KC Wong

82

Из блога Управление иерархическими данными в MySQL

Структура таблицы

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

Запрос:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

Вывод

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

Большинство пользователей в то или иное время имели дело с иерархическими данными в базе данных SQL и, несомненно, узнали, что управление иерархическими данными - это не то, для чего предназначена реляционная база данных. Таблицы реляционной базы данных не являются иерархическими (например, XML), а представляют собой просто плоский список. Иерархические данные имеют родительско-дочерние отношения, которые естественным образом не представлены в таблице реляционной базы данных. Читать далее

Обратитесь к блогу для более подробной информации.

РЕДАКТИРОВАТЬ:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

Вывод:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

Ссылка: Как сделать рекурсивный запрос SELECT в Mysql?


23
Это нормально, если в иерархии не более 4 уровней. Если есть N уровней, вы должны знать это, чтобы правильно создать запрос.
Джонатан Леффлер

2
@Damodaran, спасибо за ваш ответ ... Что мне нужно, это условие, когда число дочерних элементов неизвестно ... и в блоге, который использует концепцию внутреннего соединения, в которой требуется знать иерархию, которая не является в моем случае ... так что дайте мне знать ваше мнение о том же ... Итак, простыми словами мне нужен запрос для обработки уровней 'n' hirerachy, где 'n' неизвестно .....
Тарун Парсвани,

1
@ user3036105: это невозможно сделать в MySQL с помощью одного SQL-запроса. MySQL просто недостаточно развит для этого. Если вам это действительно нужно, рассмотрите возможность обновления до СУБД, которая поддерживает рекурсивные запросы.
a_horse_with_no_name

5
> Большинство пользователей в то или иное время имели дело с иерархическими данными в базе данных SQL и, несомненно, узнали, что управление иерархическими данными - это не то, для чего предназначена реляционная база данных. Может быть, вы имели в виду базу данных MySQL. База данных Oracle обрабатывает иерархические данные и запросы достаточно хорошо.
Питер Носко,

1
«... управление иерархическими данными - это не то, для чего предназначена реляционная база данных ...» Хотя это, возможно, и не было первоначальным намерением реляционной базы данных, в реальном мире иерархические данные невероятно распространены, и MySQL должен отражать как люди на самом деле должны использовать свои данные в реальных сценариях.
Дейв Л

9

Попробуйте эти:

Определение таблицы:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

Экспериментальные ряды:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

Хранимая рекурсивная процедура:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

Функция обертки для хранимой процедуры:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

Выберите пример:

SELECT id, name, getpath(id) AS path FROM category;

Вывод:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

Фильтрация строк по определенному пути:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Вывод:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+

1
Это не будет работать для более чем одного ребенка. напр.(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
чистый код

3
Я уверен, что это работает для более чем одного ребенка. Я даже проверил это снова.
Fandi Susanto

@ Фанди Сюзанто, спасибо, что алоэ помогает мне.
Доган Озер

9

Лучший подход, который я придумал,

  1. Используйте родословную для хранения \ сортировки \ трассировки деревьев. Этого более чем достаточно, и он работает в тысячи раз быстрее для чтения, чем любой другой подход. Это также позволяет остаться на этом паттерне, даже если DB изменится (так как ЛЮБОЙ БД позволит использовать этот паттерн)
  2. Используйте функцию, которая определяет происхождение для конкретного идентификатора.
  3. Используйте его по своему желанию (в выборках, или в операциях CUD, или даже в заданиях).

Линейный подход descr. можно найти где угодно, например здесь или здесь . Что касается функции - это то, что поразило меня.

В итоге - получилось более-менее простое, относительно быстрое и ПРОСТОЕ решение.

Тело функции

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

И тогда ты просто

select get_lineage(the_id)

Надеюсь, это поможет кому-нибудь :)


9

Сделал то же самое для другой очереди здесь

Mysql выберите рекурсивный получить все дочерние с несколькими уровнями

Запрос будет:

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;

Как мы можем это сделать? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; Я не могу отослать F1.idFolder для @pv
Рахул

Я воссоздал таблицу из исходного вопроса OP с данными, как показано в их комментарии, затем выполнил ваш запрос здесь, и в результате получил сингл NULL. Вы знаете, почему это может быть? Существуют ли предпосылки с точки зрения механизма базы данных или что-то изменилось с тех пор, как вы сделали этот ответ, что делает этот запрос устаревшим?
Цифровой ниндзя

7

Если вам нужна быстрая скорость чтения, лучше всего использовать закрывающую таблицу. Закрывающая таблица содержит строку для каждой пары предок / потомок. Так что в вашем примере таблица закрытия будет выглядеть

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

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

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

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

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


4

Простой запрос для перечисления потомков первой рекурсии:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

Результат:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

... с левым соединением:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

Решение @tincot перечислить все детские:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv) > 0
and     @pv := concat(@pv, ',', id)

Протестируйте его онлайн с Sql Fiddle и посмотрите все результаты.

http://sqlfiddle.com/#!9/a318e3/4/0


3

Вы можете сделать это таким же образом в других базах данных с помощью рекурсивного запроса (YMMV по производительности).

Другой способ сделать это - сохранить два дополнительных бита данных, левое и правое значение. Левое и правое значение получаются из предварительного обхода древовидной структуры, которую вы представляете.

Это называется измененным обходом дерева предзаказа и позволяет вам выполнить простой запрос, чтобы получить все родительские значения одновременно. Он также называется «вложенный набор».


Я хотел добавить аналогичный комментарий к вашему, но так как вы сделали это, я добавлю просто ссылку на хороший пример «вложенного набора»: mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Опока

2

Просто используйте класс php BlueM / tree для создания дерева таблицы отношений в mysql.

Tree и Tree \ Node - это PHP-классы для обработки данных, которые иерархически структурированы с использованием родительских идентификаторов. Типичным примером является таблица в реляционной базе данных, где «родительское» поле каждой записи ссылается на первичный ключ другой записи. Конечно, Tree может использовать только данные, поступающие из базы данных, но ничего: вы предоставляете данные, а Tree использует их, независимо от того, откуда поступили данные и как они были обработаны. читать далее

Вот пример использования BlueM / tree:

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...

2

введите описание изображения здесь

Это таблица категорий .

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

Вывод:: введите описание изображения здесь


1
Вы можете это объяснить? Но я гарантирую, что это работает. Спасибо.
wobsoriano

1
Пожалуйста, объясните запрос и что означает @pv ?? Как работает цикл в этом запросе?
Аманжот Каур

2
Кажется, не работает на всех уровнях, если есть дети, которые имеют более низкие идентификаторы, чем их родители. :(
Джонас

1
@Jonas занял у меня 20 минут, чтобы определить настоящую проблему, пытаясь с другой комбинацией. Да, ты прав. Он не будет работать с идентификатором ниже, чем его родительский идентификатор. У вас есть какое-нибудь решение?
Муааз

@muaaz Я наконец решил это, используя поле «путь», которое содержит путь для соответствующей строки, например, строка с идентификатором 577 имеет путь «/ 1/2/45/577 /». Если вы ищете всех потомков ID 2, вы можете просто выбрать все строки с путем LIKE "/ 1/2 /%". Единственным недостатком является то, что вы должны обновить пути в ваших методах обновления. Но для MySQL 5.6 (совместимый) это было единственное решение, которое работало для меня.
Джонас

1

Это немного сложно, проверьте, работает ли он на вас

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

Ссылка на скрипку SQL http://www.sqlfiddle.com/#!2/e3cdf/2

Замените на ваше поле и имя таблицы соответственно.


В этом случае он не будет работать sqlfiddle.com/#!2/19360/2 , с этим трюком, по крайней мере, вы должны сначала упорядочить по иерархическому уровню.
Джаугар Чанг

1

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

некоторые как:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

Пример:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

Оптимизация длины пути и ORDER BY pathиспользование кодировки base36 вместо реального числового идентификатора пути

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

Подавление также разделителя косой черты '/' с помощью фиксированной длины и дополнения к закодированному идентификатору

Подробное описание оптимизации здесь: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

ДЕЛАТЬ

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


Спасибо! Интересно сbase36
Влад

0

Это работает для меня, надеюсь, это будет работать и для вас. Это даст вам Record set Root to Child для любого конкретного меню. Измените имя поля в соответствии с вашими требованиями.

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;

Кажется, не работает на всех уровнях, если есть дети, которые имеют более высокие идентификаторы, чем их родители
muaaz

-1

Мне было легче:

1) создать функцию, которая будет проверять, находится ли элемент где-либо в родительской иерархии другого элемента. Примерно так (я не буду писать функцию, сделайте это с помощью WHILE DO):

is_related(id, parent_id);

в вашем примере

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2) использовать суб-выбор, что-то вроде этого:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));

-1

Я сделал запрос для вас. Это даст вам рекурсивную категорию с одним запросом:

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

Вот скрипка .


Пожалуйста, удалите / отредактируйте свой ответ, чтобы вернуть себе положительную репутацию.
fWd82
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.