Làm cách nào để thực hiện truy vấn SELECT đệ quy trong MySQL?


79

Tôi có một bảng sau:

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

Nếu người dùng tìm kiếm "1", chương trình sẽ nhìn vào col1giá trị có "1", sau đó sẽ nhận giá trị là col3"5", sau đó chương trình sẽ tiếp tục tìm kiếm "5" col1và sẽ nhận được "3" trong col3, và như vậy. Vì vậy, nó sẽ in ra:

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

Nếu người dùng tìm kiếm "6", nó sẽ in ra:

6   | o   | 2
2   | 0   | 8

Làm thế nào để xây dựng một SELECTtruy vấn để làm điều đó?


Có một giải pháp cho vấn đề của bạn trong bài đăng này stackoverflow.com/questions/14658378/recursive-mysql-select
medina

Câu trả lời:


70

Biên tập

Giải pháp được đề cập bởi @leftclickben cũng hiệu quả. Chúng tôi cũng có thể sử dụng một thủ tục được lưu trữ cho tương tự.

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 //

Chúng tôi đang sử dụng bảng tạm thời để lưu trữ kết quả của đầu ra và vì bảng tạm thời dựa trên phiên, chúng tôi sẽ không có bất kỳ vấn đề nào liên quan đến dữ liệu đầu ra không chính xác.

SQL FIDDLE Demo

Hãy thử truy vấn này:

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_idGiá trị ghi chú phải nhỏ hơn giá trị child_idđể giải pháp này hoạt động.


2
Mọi người Vui lòng đánh dấu câu trả lời này là một giải pháp tối ưu vì một số giải pháp khác của câu hỏi tương tự (về Lựa chọn đệ quy trong mysql) khá phức tạp vì nó yêu cầu tạo bảng và chèn dữ liệu vào đó. Giải pháp này rất thanh lịch.
Tum

5
Chỉ cần cẩn thận một lần với lời giải của mình, không có phụ thuộc kiểu chu trình thì nó sẽ chuyển sang vòng lặp vô hạn và một điều nữa là nó chỉ tìm được 1 bản ghi col3kiểu đó nên nếu có nhiều bản ghi thì sẽ không được.
Meherzad

2
@HamidSarfraz hiện đã hoạt động sqlfiddle.com/#!2/74f457/14 . Điều này sẽ làm việc cho bạn. Đối với tìm kiếm tuần tự và id sẽ luôn có giá trị lớn hơn giá trị gốc, vì nguồn gốc cần được tạo trước. Xin vui lòng thông báo nếu bạn cần thêm bất kỳ chi tiết.
Meherzad

5
Đây không phải là một giải pháp. Đó chỉ là một tác dụng phụ may mắn của việc quét bảng. Đọc kỹ câu trả lời của @leftclickben nếu không bạn sẽ lãng phí rất nhiều thời gian như tôi đã làm.
jaeheung

2
Tum Tôi biết cách hoạt động của SQL đệ quy. MySQL chưa triển khai CTE đệ quy, vì vậy một tùy chọn khả thi là tùy chọn trong liên kết bạn đã cung cấp (sử dụng các thủ tục / hàm được lưu trữ). Một cách khác là sử dụng các biến mysql. Tuy nhiên, câu trả lời ở đây không phải là tao nhã mà ngược lại, chỉ là kinh khủng. Nó không hiển thị SQL đệ quy. Nếu nó hoạt động trong trường hợp của bạn, thì đó chỉ là tình cờ, như @jaehung đã chỉ ra một cách chính xác. Và tôi không ngại những câu trả lời kinh khủng. Tôi chỉ phản đối họ. Nhưng một câu trả lời khủng khiếp ở mức +50, tôi thực sự phiền.
ypercubeᵀᴹ

51

Câu trả lời được chấp nhận bởi @Meherzad chỉ hoạt động nếu dữ liệu theo một thứ tự cụ thể. Nó xảy ra với dữ liệu từ câu hỏi OP. Trong trường hợp của tôi, tôi đã phải sửa đổi nó để hoạt động với dữ liệu của mình.

Lưu ý Điều này chỉ hoạt động khi "id" của mọi bản ghi (col1 trong câu hỏi) có giá trị LỚN HƠN "id cha" của bản ghi đó (col3 trong câu hỏi). Trường hợp này thường xảy ra, vì thông thường cha mẹ sẽ cần được tạo trước. Tuy nhiên, nếu ứng dụng của bạn cho phép thay đổi hệ thống phân cấp, nơi một mục có thể được đặt lại ở một nơi khác, thì bạn không thể dựa vào điều này.

Đây là truy vấn của tôi trong trường hợp nó giúp ích cho ai đó; lưu ý rằng nó không hoạt động với câu hỏi đã cho vì dữ liệu không tuân theo cấu trúc bắt buộc được mô tả ở trên.

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

Sự khác biệt là table1được sắp xếp theo thứ tự col1để cha mẹ sẽ theo sau nó (vì col1giá trị của cha mẹ thấp hơn giá trị của con).


u đúng, còn nếu một đứa trẻ có 2 cha mẹ, sau đó nó có thể không chọn cả hai
Tum

Cảm ơn anh bạn. Teamworek đã thực hiện hành động của mình trong bài đăng này! Tôi đã làm cho nó hoạt động khi tôi thay đổi giá trị của @pv. Đó chính xác là những gì tôi đang tìm kiếm.
Mohamed Ennahdi El Idrissi 14/09/15

Điều gì sẽ xảy ra nếu tôi muốn sử dụng cột này làm cột group_concat của ID mẹ cho mỗi hàng trong một lựa chọn lớn hơn (nghĩa là giá trị của biến @pv là động cho mỗi hàng). Tham gia vào truy vấn con không biết cột chính (mà tôi cố gắng kết nối với), sử dụng một biến khác, nó cũng không hoạt động (luôn trả về NULL)
qdev 22/10/15

Tôi đã tạo ra một chức năng tùy chỉnh mà tạo ra con đường cây sử dụng group_concat, và bây giờ tôi có thể gửi thông số như giá trị cột cho mỗi hàng;)
qdev

Bạn nghĩ gì về câu trả lời mới mà tôi đã đăng? Không phải là của bạn không tốt, nhưng tôi chỉ muốn có một SELECT có thể hỗ trợ id cha> id con.
Master DJon

18

Câu trả lời leftclickben phù hợp với tôi, nhưng tôi muốn một đường dẫn từ một nút nhất định sao lưu cây đến gốc và chúng dường như đi theo hướng khác, xuống cây. Vì vậy, tôi phải lật một số trường xung quanh và đổi tên cho rõ ràng, và điều này phù hợp với tôi, trong trường hợp đây là điều mà bất kỳ ai khác cũng muốn--

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;

cho:

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

@ BoB3K, điều này có hoạt động không nếu các ID không nhất thiết phải theo "thứ tự". Nó dường như không hoạt động trong trường hợp id của cha mẹ dọc theo chuỗi cao hơn thì con của nó? Ví dụ: chuỗi 1> 120 > 112 sẽ chỉ trả về ((112, 120)) trong khi 2> 22> 221 trả về chuỗi đầy đủ ((221,22), (22,2), (2, null))
Tomer Cagan

Đã lâu rồi, nhưng tôi nghĩ rằng tôi nhớ đã đọc trong các câu trả lời ban đầu rằng điều này không hoạt động nếu id mục không theo thứ tự, điều này thường không phải là vấn đề nếu id là khóa tăng tự động.
BoB3K

Nó hoạt động tốt và tôi sử dụng nó cho trang web của mình ... vấn đề ở đây là không thể sắp xếp kết quả ASC. 1 3 6Tôi sử dụng array_reverse()trong php thay vì ..... bất kỳ giải pháp sql nào cho điều đó?
joe

8

Thủ tục lưu trữ là cách tốt nhất để làm điều đó. Bởi vì giải pháp của Meherzad sẽ chỉ hoạt động nếu dữ liệu tuân theo cùng một thứ tự.

Nếu chúng ta có cấu trúc bảng như thế này

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

Nó sẽ không hoạt động. SQL Fiddle Demo

Đây là một mã quy trình mẫu để đạt được điều tương tự.

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;

Đây là một giải pháp mạnh mẽ và tôi đang sử dụng nó mà không gặp khó khăn gì. Bạn có thể vui lòng giúp tôi khi đi theo hướng khác, tức là xuống cây - Tôi tìm thấy tất cả các hàng có id cha == inputNo, nhưng nhiều ID có thể có một ID cha.
mils

8

Nếu bạn muốn có thể có một SELECT mà không gặp vấn đề về id cha phải thấp hơn id con, một hàm có thể được sử dụng. Nó cũng hỗ trợ nhiều con (như một cây nên làm) và cây có thể có nhiều đầu. Nó cũng đảm bảo ngắt nếu tồn tại một vòng lặp trong dữ liệu.

Tôi muốn sử dụng SQL động để có thể chuyển tên bảng / cột, nhưng các hàm trong MySQL không hỗ trợ điều này.

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$$

Ở đây, bảng testphải được sửa đổi thành tên bảng thực và các cột (ParentId, Id) có thể phải được điều chỉnh cho tên thật của bạn.

Sử dụng :

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

Kết quả :

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

SQL để tạo thử nghiệm:

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');

CHỈNH SỬA: Đây là một mẹo nhỏ để tự mình kiểm tra. Nó buộc tôi phải thay đổi dấu phân cách bằng cách sử dụng dấu phân cách được xác định trước, nhưng nó hoạt động.


0

Xây dựng từ Master DJon

Đây là chức năng đơn giản hóa cung cấp tiện ích bổ sung là trả về độ sâu (trong trường hợp bạn muốn sử dụng logic để bao gồm tác vụ mẹ hoặc tìm kiếm ở độ sâu cụ thể)

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$$

Sử dụng:

select * from test where childDepth(1, id) <> -1;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.