MySQL에서 재귀 SELECT 쿼리를 수행하는 방법은 무엇입니까?


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
People Pls는 비슷한 질문의 다른 솔루션 (mysql의 Recursive Select에 대한)이 테이블을 만들고 데이터를 삽입해야하므로 매우 복잡하기 때문에이 답변을 최적의 솔루션으로 표시합니다. 이 솔루션은 매우 우아합니다.
Tum

5
그의 솔루션에 한 가지주의를 기울이십시오.주기 유형 종속성이 없으면 무한 루프로 이동하고 한 가지 더 해당 col3유형의 레코드 1 개만 찾을 수 있으므로 여러 레코드가 있으면 작동하지 않습니다.
Meherzad 2013 년

2
@HamidSarfraz는 이제 sqlfiddle.com/#!2/74f457/14 작동 합니다 . 이것은 당신을 위해 작동합니다. 순차 검색과 마찬가지로 부모가 먼저 생성되어야하므로 id는 항상 부모보다 큰 가치를 갖습니다. 추가 세부 정보가 필요하면 알려주십시오.
Meherzad

5
이것은 해결책이 아닙니다. 테이블 스캔의 운이 좋은 부작용 일뿐입니다. @leftclickben의 답변을주의 깊게 읽으십시오. 그렇지 않으면 내가 한 것처럼 많은 시간을 낭비하게 될 것입니다.
jaeheung

2
재귀 SQL이 어떻게 작동하는지 알고 있습니다. MySQL은 재귀 적 CTE를 구현하지 않았으므로 실행 가능한 옵션 중 하나는 제공 한 링크 (저장 프로 시저 / 함수 사용)에있는 옵션입니다. 다른 하나는 mysql 변수를 사용하는 것입니다. 그러나 여기에 대한 대답은 우아하지 않고 그 반대이며 끔찍합니다. 재귀 SQL을 표시하지 않습니다. 귀하의 경우에 효과가 있었다면 @jaehung이 올바르게 지적했듯이 우연히 발생한 것입니다. 그리고 나는 끔찍한 대답을 신경 쓰지 않습니다. 나는 그들을 반대 투표합니다. 그러나 +50의 끔찍한 대답은 괜찮습니다.
ypercubeᵀᴹ

51

@Meherzad가 수락 한 답변은 데이터가 특정 순서 인 경우에만 작동합니다. OP 질문의 데이터로 작업합니다. 제 경우에는 데이터 작업을 위해 수정해야했습니다.

참고 이것은 모든 레코드의 "id"(질문의 col1) 값이 해당 레코드의 "parent 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입니다.


u 맞습니다, 또한 아이가 2 명의 부모를 가지고 있다면, 둘 다 선택하지 않을 수 있습니다
Tum

고마워요. Teamworek이이 게시물에서 작업을 수행했습니다! @pv의 값을 변경했을 때 작동하도록했습니다. 그게 바로 제가 찾던 것입니다.
Mohamed Ennahdi El Idrissi

더 큰 선택에서 각 행에 대한 부모 ID의 group_concat 열로 사용하려면 어떻게해야합니까 (즉, @pv 변수의 값이 각 행에 대해 동적 임). 하위 쿼리의 조인은 마스터 열 (내가 연결하려고하는)을 알지 못하며 작동하지 않는 다른 변수를 사용합니다 (항상 NULL을 반환)
qdev

group_concat을 사용하여 트리 경로를 생성하는 사용자 지정 함수를 만들었으며 이제 각 행의 열 값을 매개 변수로 보낼 수 있습니다.)
qdev

내가 게시 한 새 답변에 대해 어떻게 생각하십니까? 당신의 것이 좋지 않다는 것은 아니지만 부모 ID> 자식 ID를 지원할 수있는 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, ID가 반드시 "순서"에 있지 않은 경우이 작업이 작동합니다. 사슬을 따라 부모의 이드가 자식보다 높으면 작동하지 않는 것 같나요? 예 : 체인 1> 120 > 112는 ((112, 120)) 만 반환하는 반면 2> 22> 221은 전체 체인을 반환합니다 ((221,22), (22,2), (2, null))
Tomer Cagan

시간이 오래되었지만 항목 ID가 순서가 맞지 않으면 작동하지 않는다는 원래 답변을 읽은 것을 기억합니다 .ID가 자동 증가 키이면 일반적으로 문제가되지 않습니다.
BoB3K

그것은 잘 작동하고 내 사이트에 사용합니다 ... 여기서 문제는 결과 ASC를 주문할 수 없다는 것입니다. 1 3 6내가 사용 array_reverse()..... 대신 PHP에서 그것에 대해 모든 SQL 솔루션을?
joe

8

저장 프로시 저는이를 수행하는 가장 좋은 방법입니다. Meherzad의 솔루션은 데이터가 동일한 순서를 따르는 경우에만 작동하기 때문입니다.

이와 같은 테이블 구조가 있다면

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 인 모든 행을 찾았지만 많은 ID가 하나의 부모 ID를 가질 수 있습니다.
mils

8

상위 ID가 하위 ID보다 낮아야하는 문제없이 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');

편집 : 여기에 직접 테스트 하는 바이올린 이 있습니다. 사전 정의 된 구분 기호를 사용하여 구분자를 변경해야했지만 작동합니다.


0

Master 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;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.