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


79

Получилась следующая таблица:

col1 | col2 | col3
-----+------+-------
1    | a    | 5
5    | d    | 3
3    | k    | 7
6    | o    | 2
2    | 0    | 8

Если пользователь ищет «1», программа будет смотреть на то, col1что имеет «1», затем получит значение col3«5», затем программа продолжит поиск «5» в col1и получит «3». в col3и так далее. Итак, он распечатает:

1   | a   | 5
5   | d   | 3
3   | k   | 7

Если пользователь ищет «6», он распечатает:

6   | o   | 2
2   | 0   | 8

Как построить SELECTзапрос для этого?


В этом сообщении есть решение вашей проблемы stackoverflow.com/questions/14658378/recursive-mysql-select
medina

Ответы:


70

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

Решение, упомянутое @leftclickben, также эффективно. Мы также можем использовать для этого хранимую процедуру.

CREATE PROCEDURE get_tree(IN id int)
 BEGIN
 DECLARE child_id int;
 DECLARE prev_id int;
 SET prev_id = id;
 SET child_id=0;
 SELECT col3 into child_id 
 FROM table1 WHERE col1=id ;
 create TEMPORARY  table IF NOT EXISTS temp_table as (select * from table1 where 1=0);
 truncate table temp_table;
 WHILE child_id <> 0 DO
   insert into temp_table select * from table1 WHERE col1=prev_id;
   SET prev_id = child_id;
   SET child_id=0;
   SELECT col3 into child_id
   FROM TABLE1 WHERE col1=prev_id;
 END WHILE;
 select * from temp_table;
 END //

Мы используем временную таблицу для хранения результатов вывода, и поскольку временные таблицы основаны на сеансах, у нас не будет проблем с неверными данными вывода.

SQL FIDDLE Demo

Попробуйте этот запрос:

SELECT 
    col1, col2, @pv := col3 as 'col3' 
FROM 
    table1
JOIN 
    (SELECT @pv := 1) tmp
WHERE 
    col1 = @pv

SQL FIDDLE Demo:

| COL1 | COL2 | COL3 |
+------+------+------+
|    1 |    a |    5 |
|    5 |    d |    3 |
|    3 |    k |    7 |

Обратите внимание,
parent_id значение должно быть меньше, чем child_idэто решение работает.


2
Люди, пожалуйста, отметьте этот ответ как оптимальное решение, поскольку некоторые другие решения аналогичного вопроса (о рекурсивном выборе в mysql) довольно сложны, поскольку для этого требуется создать таблицу и вставить в нее данные. Это решение очень элегантное.
Tum

5
Просто позаботьтесь о его решении, нет зависимости типа цикла, тогда он перейдет в бесконечный цикл, и еще одна вещь, он найдет только 1 запись этого col3типа, поэтому, если есть несколько записей, это не сработает.
Meherzad

2
@HamidSarfraz теперь работает sqlfiddle.com/#!2/74f457/14 . Это сработает для вас. Как и в случае последовательного поиска, идентификатор всегда будет иметь большее значение, чем родительский, так как родительский элемент должен быть создан первым. Пожалуйста, сообщите, если вам нужны дополнительные сведения.
Meherzad

5
Это не выход. Это просто счастливый побочный эффект сканирования таблицы. Внимательно прочтите ответ @leftclickben, иначе вы потратите много времени, как я.
jaeheung

2
Там я знаю, как работает рекурсивный SQL. MySQL не реализовал рекурсивные CTE, поэтому один из возможных вариантов - вариант, указанный в указанной вами ссылке (с использованием хранимых процедур / функций). Другой использует переменные mysql. Однако ответ здесь не изящный, а наоборот, просто ужасный. Он не показывает рекурсивный SQL. Если это сработало в вашем случае, это было только случайно, как правильно указал @jaehung. И я не против ужасных ответов. Я просто проголосовал против них. Но ужасный ответ на +50, я не против.
ypercubeᵀᴹ

51

Принятый ответ @Meherzad работает, только если данные находятся в определенном порядке. Бывает работать с данными из вопроса OP. В моем случае мне пришлось изменить его для работы с моими данными.

Запись Это работает только тогда, когда каждая запись «id» (col1 в вопросе) имеет значение БОЛЬШЕ, чем «родительский id» этой записи (col3 в вопросе). Это часто бывает, потому что обычно сначала нужно создать родительский элемент. Однако, если ваше приложение допускает изменения в иерархии, где элемент может быть повторно создан в другом месте, вы не можете полагаться на это.

Это мой запрос на случай, если он кому-то поможет; обратите внимание, что это не работает с данным вопросом, потому что данные не соответствуют требуемой структуре, описанной выше.

select t.col1, t.col2, @pv := t.col3 col3
from (select * from table1 order by col1 desc) t
join (select @pv := 1) tmp
where t.col1 = @pv

Разница в том, что table1он упорядочивается col1так, что родитель будет после него (поскольку значение родительского col1элемента ниже, чем значение дочернего элемента).


Ты правильно, если у ребенка 2 родителя, он не может выбрать обоих
Тум

Спасибо чувак. В этом посте Teamworek сделал свое дело! Я заставил его работать, когда я изменил значение @pv. Именно это я и искал.
Мохамед Эннахди Эль Идрисси

Что, если я хочу использовать это как столбец group_concat родительских идентификаторов для каждой строки в большем выборе (это означает, что значение переменной @pv будет динамическим для каждой строки). Соединение в подзапросе не знает главного столбца (к которому я пытаюсь подключиться), используя другую переменную, он тоже не работает (всегда возвращает NULL)
qdev

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

Что вы думаете о новом ответе, который я опубликовал? Не то, чтобы ваш нехороший, но я хотел иметь только SELECT, который мог бы поддерживать родительский идентификатор> дочерний идентификатор.
Master DJon

18

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

item | parent
-------------
1    | null
2    | 1
3    | 1
4    | 2
5    | 4
6    | 3

и

select t.item_id as item, @pv:=t.parent as parent
from (select * from item_tree order by item_id desc) t
join
(select @pv:=6)tmp
where t.item_id=@pv;

дает:

item | parent
-------------
6    | 3
3    | 1
1    | null

@ BoB3K, это сработает, если идентификаторы не обязательно расположены в "порядке". Кажется, не работает, если идентификатор родительского элемента в цепочке выше, чем его дочерний? Например, цепочка 1> 120 > 112 вернет только ((112, 120)), а 2> 22> 221 вернет полную цепочку ((221,22), (22,2), (2, null))
Томер Каган

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

Он работает хорошо, и я использую его для своего сайта ... проблема здесь в том, что невозможно заказать результаты ASC. Вместо этого 1 3 6я использую array_reverse()в php ..... любое решение sql для этого?
joe

8

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

Если у нас есть такая структура таблицы

col1 | col2 | col3
-----+------+------
 3   | k    | 7
 5   | d    | 3
 1   | a    | 5
 6   | o    | 2
 2   | 0    | 8

Это не сработает. SQL Fiddle Demo

Вот пример кода процедуры для достижения того же.

delimiter //
CREATE PROCEDURE chainReaction 
(
    in inputNo int
) 
BEGIN 
    declare final_id int default NULL;
    SELECT col3 
    INTO final_id 
    FROM table1
    WHERE col1 = inputNo;
    IF( final_id is not null) THEN
        INSERT INTO results(SELECT col1, col2, col3 FROM table1 WHERE col1 = inputNo);
        CALL chainReaction(final_id);   
    end if;
END//
delimiter ;

call chainReaction(1);
SELECT * FROM results;
DROP TABLE if exists results;

Это надежное решение, и я использую его без проблем. Не могли бы вы помочь мне, когда идете в другом направлении, то есть вниз по дереву - я нахожу все строки, в которых родительский id == inputNo, но многие идентификаторы могут иметь один родительский идентификатор.
mils

8

Если вы хотите иметь возможность иметь SELECT без проблем, когда родительский идентификатор должен быть ниже, чем дочерний идентификатор, можно использовать функцию. Он также поддерживает несколько дочерних элементов (как и должно делать дерево), и дерево может иметь несколько голов. Это также гарантирует прерывание, если в данных существует цикл.

Я хотел использовать динамический SQL, чтобы иметь возможность передавать имена таблиц / столбцов, но функции в MySQL не поддерживают это.

DELIMITER $$

CREATE FUNCTION `isSubElement`(pParentId INT, pId INT) RETURNS int(11)
DETERMINISTIC    
READS SQL DATA
BEGIN
DECLARE isChild,curId,curParent,lastParent int;
SET isChild = 0;
SET curId = pId;
SET curParent = -1;
SET lastParent = -2;

WHILE lastParent <> curParent AND curParent <> 0 AND curId <> -1 AND curParent <> pId AND isChild = 0 DO
    SET lastParent = curParent;
    SELECT ParentId from `test` where id=curId limit 1 into curParent;

    IF curParent = pParentId THEN
        SET isChild = 1;
    END IF;
    SET curId = curParent;
END WHILE;

RETURN isChild;
END$$

Здесь таблица test должна быть изменена на настоящее имя таблицы, а столбцы (ParentId, Id), возможно, придется скорректировать для ваших настоящих имен.

Применение :

SET @wantedSubTreeId = 3;
SELECT * FROM test WHERE isSubElement(@wantedSubTreeId,id) = 1 OR ID = @wantedSubTreeId;

Результат:

3   7   k
5   3   d
9   3   f
1   5   a

SQL для создания теста:

CREATE TABLE IF NOT EXISTS `test` (
  `Id` int(11) NOT NULL,
  `ParentId` int(11) DEFAULT NULL,
  `Name` varchar(300) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

insert into test (id, parentid, name) values(3,7,'k');
insert into test (id, parentid, name) values(5,3,'d');
insert into test (id, parentid, name) values(9,3,'f');
insert into test (id, parentid, name) values(1,5,'a');
insert into test (id, parentid, name) values(6,2,'o');
insert into test (id, parentid, name) values(2,8,'c');

EDIT: вот скрипка, чтобы проверить это самостоятельно. Это заставило меня изменить разделитель на предопределенный, но он работает.


0

Строительство Мастера DJon

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

DELIMITER $$
FUNCTION `childDepth`(pParentId INT, pId INT) RETURNS int(11)
    READS SQL DATA
    DETERMINISTIC
BEGIN
DECLARE depth,curId int;
SET depth = 0;
SET curId = pId;

WHILE curId IS not null AND curId <> pParentId DO
    SELECT ParentId from test where id=curId limit 1 into curId;
    SET depth = depth + 1;
END WHILE;

IF curId IS NULL THEN
    set depth = -1;
END IF;

RETURN depth;
END$$

Применение:

select * from test where childDepth(1, id) <> -1;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.