it-swarm.com.ru

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

Я получил следующую таблицу:

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 для этого?

77
Tum

Правка

Решение, упомянутое @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, чтобы это решение работало.

67
Meherzad

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

Примечание Это работает, только когда «id» каждой записи (столбец col1 в вопросе) имеет значение БОЛЬШЕ, чем «родительский идентификатор» этой записи (столбец 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 у родителя меньше, чем у дочернего элемента).

50
leftclickben

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

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

а также

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

дает:

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

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

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

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;
7
Jazmin

Если вы хотите иметь возможность иметь 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');

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

6
Master DJon

Здание от Мастер 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;
0
patrick