Cách tạo truy vấn đệ quy phân cấp MySQL


271

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

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

Bây giờ, tôi muốn có một truy vấn MySQL duy nhất mà tôi chỉ cần cung cấp id [ví dụ: 'id = 19'] thì tôi sẽ nhận được tất cả các id con của nó [tức là kết quả nên có id '20, 21,22 ']. ... Ngoài ra, hệ thống phân cấp của trẻ em không được biết nó có thể thay đổi ....

Ngoài ra, tôi đã có giải pháp sử dụng vòng lặp for ..... Hãy cho tôi biết cách đạt được điều tương tự bằng một truy vấn MySQL duy nhất nếu có thể.


Giả sử hệ thống phân cấp sâu 7 cấp. Bạn mong đợi bảng đầu ra trông như thế nào?
Jonathan Leffler

1
MySQL (vẫn) không hỗ trợ các truy vấn phân cấp (như các DBMS hiện đại khác làm). Bạn sẽ cần phải viết một thủ tục được lưu trữ hoặc sử dụng một kiểu dữ liệu khác.
a_horse_with_no_name


1
MYSQL 8.0 sẽ hỗ trợ truy vấn đệ quy bằng CTE (Biểu thức bảng chung)
user3712320

Điều gì về việc nhận được danh sách đầy đủ các bài đăng bắt đầu từ id bình luận cuối cùng? Hay đứa con cuối cùng?
joe

Câu trả lời:


393

Đối với MySQL 8+: sử dụng withcú pháp đệ quy .
Đối với MySQL 5.x: sử dụng biến nội tuyến, ID đường dẫn hoặc tự tham gia.

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;

Giá trị được chỉ định trong parent_id = 19phải được đặt thànhid cha mẹ bạn muốn chọn tất cả các hậu duệ của.

MySQL 5.x

Đối với các phiên bản MySQL không hỗ trợ Biểu thức bảng chung (tối đa phiên bản 5.7), bạn sẽ đạt được điều này với truy vấn sau:

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

Đây là một câu đố .

Ở đây, giá trị được chỉ định trong @pv := '19'phải được đặt thànhid cha mẹ bạn muốn chọn tất cả các hậu duệ của.

Điều này cũng sẽ làm việc nếu cha mẹ có nhiều con. Tuy nhiên, yêu cầu mỗi bản ghi phải đáp ứng điều kiện parent_id < id, nếu không kết quả sẽ không được hoàn thành.

Bài tập biến trong một truy vấn

Truy vấn này sử dụng cú pháp MySQL cụ thể: các biến được gán và sửa đổi trong quá trình thực thi. Một số giả định được đưa ra về thứ tự thực hiện:

  • Điều fromkhoản được đánh giá đầu tiên. Vì vậy, đó là nơi @pvđược khởi tạo.
  • Các wherekhoản được đánh giá cho mỗi bản ghi theo thứ tự thu hồi từ các frombí danh. Vì vậy, đây là nơi một điều kiện được đưa vào chỉ bao gồm các bản ghi mà cha mẹ đã được xác định là đang ở trong cây con cháu (tất cả các hậu duệ của cha mẹ chính được thêm dần vào @pv).
  • Các điều kiện trong wheremệnh đề này được đánh giá theo thứ tự và việc đánh giá bị gián đoạn một khi tổng kết quả là chắc chắn. Do đó, điều kiện thứ hai phải ở vị trí thứ hai, vì nó thêm iddanh sách cha mẹ và điều này chỉ xảy ra nếu idvượt qua điều kiện đầu tiên. Các lengthchức năng được chỉ gọi là để đảm bảo điều kiện này luôn là sự thật, ngay cả khi pvchuỗi sẽ vì một lý do mang lại một giá trị falsy.

Nhìn chung, người ta có thể thấy những giả định này quá rủi ro để dựa vào. Các tài liệu cảnh báo:

bạn có thể nhận được kết quả mà bạn mong đợi, nhưng điều này không được đảm bảo [...] thứ tự đánh giá cho các biểu thức liên quan đến các biến người dùng không được xác định.

Vì vậy, mặc dù nó hoạt động nhất quán với truy vấn trên, thứ tự đánh giá vẫn có thể thay đổi, ví dụ khi bạn thêm điều kiện hoặc sử dụng truy vấn này làm chế độ xem hoặc truy vấn phụ trong truy vấn lớn hơn. Đây là một "tính năng" sẽ bị xóa trong bản phát hành MySQL trong tương lai :

Các bản phát hành trước đây của MySQL cho phép gán giá trị cho biến người dùng trong các câu lệnh khác SET. Chức năng này được hỗ trợ trong MySQL 8.0 để tương thích ngược nhưng có thể bị loại bỏ trong bản phát hành MySQL trong tương lai.

Như đã nói ở trên, từ MySQL 8.0 trở đi, bạn nên sử dụng đệ quy with cú pháp .

Hiệu quả

Đối với các tập dữ liệu rất lớn, giải pháp này có thể bị chậm, vì find_in_setthao tác không phải là cách lý tưởng nhất để tìm số trong danh sách, chắc chắn không phải trong danh sách đạt kích thước theo thứ tự độ lớn như số lượng bản ghi được trả về.

Phương án 1 : with recursive,connect by

Ngày càng có nhiều cơ sở dữ liệu triển khai cú pháp tiêu chuẩn ISO SQL: 1999WITH [RECURSIVE] cho các truy vấn đệ quy (ví dụ Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperQuery 2.1.0+ , Teradata , MariaDB 10.2.2+ ). Và kể từ phiên bản 8.0, MySQL cũng hỗ trợ nó . Xem đầu câu trả lời này để biết cú pháp sử dụng.

Một số cơ sở dữ liệu có một cú pháp thay thế, không chuẩn cho các tra cứu phân cấp, chẳng hạn như CONNECT BYmệnh đề có sẵn trên Oracle , DB2 , Informix , CUBRID và các cơ sở dữ liệu khác.

Phiên bản MySQL 5.7 không cung cấp tính năng như vậy. Khi công cụ cơ sở dữ liệu của bạn cung cấp cú pháp này hoặc bạn có thể di chuyển sang một cú pháp đó, thì đó chắc chắn là lựa chọn tốt nhất để thực hiện. Nếu không, sau đó cũng xem xét các lựa chọn thay thế sau đây.

Phương án 2: Mã định danh kiểu đường dẫn

Mọi thứ trở nên dễ dàng hơn rất nhiều nếu bạn chỉ định idcác giá trị có chứa thông tin phân cấp: một đường dẫn. Ví dụ, trong trường hợp của bạn, nó có thể trông như thế này:

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

Sau đó, bạn selectsẽ trông như thế này:

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

Phương án 3: Tự lặp đi lặp lại

Nếu bạn biết giới hạn trên cho việc cây phân cấp của bạn có thể trở nên sâu đến mức nào, bạn có thể sử dụng sqltruy vấn tiêu chuẩn như thế này:

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;

Xem câu đố này

Điều wherekiện chỉ định cha mẹ mà bạn muốn lấy con cháu của. Bạn có thể mở rộng truy vấn này với nhiều cấp độ hơn nếu cần.


26
Tôi thích lời giải thích của bạn. Nó không chỉ đưa ra một câu trả lời, nó giải thích tại sao nó giải quyết vấn đề để chúng ta thực sự có thể học hỏi từ nó. EDIT: thật tuyệt khi nó không phụ thuộc vào việc biết trước số lượng cấp độ.
Byson

1
@Bison, tôi rất đánh giá cao phản hồi của bạn. Cảm ơn!
trincot

2
@ Avión, nó không phải là thứ bạn phải đặt ở đâu đó, nó là một yêu cầu mà đối với tất cả các hồ sơ điều kiện này là đúng. Nếu bạn có một hoặc nhiều hồ sơ parent_id > idthì bạn không thể sử dụng giải pháp này.
trincot

2
Đối với bất kỳ ai đang tìm cách sử dụng WITH RECURSIVEphương pháp này, tôi thấy bài viết sau đây thực sự hữu ích với các tình huống khác nhau như độ sâu đệ quy, phân biệt và phát hiện và đóng chu kỳ
Horse

2
Tôi đã thử giải pháp chính trên MySQL5.7 trên máy tính của mình, trên các bảng của riêng tôi, nhưng nó không hoạt động do tương đương với mệnh đề @pv: = concat (@pv, ',', id) đánh giá là sai. Tôi đã sửa nó bằng cách thay đổi nó thành chiều dài (@pv: = concat (@pv, ',', id))> 0 để nó luôn đúng.
KC Wong

82

Từ blog Quản lý dữ liệu phân cấp trong MySQL

Cấu trúc bảng

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

Truy vấn:

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

Đầu ra

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

Hầu hết người dùng lúc này hay lúc khác đã xử lý dữ liệu phân cấp trong cơ sở dữ liệu SQL và không nghi ngờ gì khi biết rằng việc quản lý dữ liệu phân cấp không phải là mục đích của cơ sở dữ liệu quan hệ. Các bảng của cơ sở dữ liệu quan hệ không phân cấp (như XML), mà chỉ đơn giản là một danh sách phẳng. Dữ liệu phân cấp có mối quan hệ cha-con không được biểu diễn tự nhiên trong bảng cơ sở dữ liệu quan hệ. Đọc thêm

Tham khảo blog để biết thêm chi tiết.

BIÊN TẬP:

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

Đầu ra:

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

Tài liệu tham khảo: Làm thế nào để thực hiện truy vấn đệ quy CHỌN trong Mysql?


23
Điều đó tốt miễn là không có nhiều hơn 4 cấp trong phân cấp. Nếu có N cấp độ, bạn phải biết rằng để tạo truy vấn đúng.
Jonathan Leffler

2
@Damodaran, Cảm ơn bạn đã trả lời ... Điều tôi cần là một điều kiện mà số lượng trẻ em không được biết đến ... và trong blog đang sử dụng một khái niệm tham gia bên trong trong đó phân cấp được yêu cầu phải biết không phải là thứ bậc trong trường hợp của tôi ... vì vậy hãy cho tôi biết quan điểm của bạn giống nhau ... Vì vậy, nói một cách đơn giản, tôi cần một truy vấn để xử lý các cấp độ 'n' hirerachy trong đó 'n' không được biết đến .....
Tarun Parswani

1
@ user3036105: không thể thực hiện điều này trong MySQL với một truy vấn SQL duy nhất . MySQL đơn giản là không đủ tiến bộ cho điều đó. Nếu bạn thực sự cần điều này, hãy xem xét nâng cấp lên DBMS hỗ trợ các truy vấn đệ quy.
a_horse_with_no_name

5
> Hầu hết người dùng lúc này hay lúc khác đã xử lý dữ liệu phân cấp trong cơ sở dữ liệu SQL và không nghi ngờ gì khi biết rằng việc quản lý dữ liệu phân cấp không phải là mục đích của cơ sở dữ liệu quan hệ. Có lẽ bạn có nghĩa là một cơ sở dữ liệu MySQL. Một cơ sở dữ liệu Oracle xử lý dữ liệu phân cấp và truy vấn khá tốt.
Peter Nosko

1
"... việc quản lý dữ liệu phân cấp không phải là cơ sở dữ liệu quan hệ dành cho ..." Mặc dù điều này có thể không phải là mục đích ban đầu của cơ sở dữ liệu quan hệ, nhưng trong dữ liệu phân cấp trong thế giới thực là rất phổ biến và MySQL sẽ phản ánh mọi người thực sự cần sử dụng dữ liệu của họ như thế nào trong các tình huống thực tế.
Dave L

9

Thử những thứ này xem:

Bảng định nghĩa:

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;

Hàng thử nghiệm:

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

Thủ tục lưu trữ đệ quy:

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 ;

Hàm Wrapper cho thủ tục được lưu trữ:

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 ;

Chọn ví dụ:

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

Đầu ra:

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

Lọc các hàng với đường dẫn nhất định:

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

Đầu ra:

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

1
Điều này sẽ không làm việc cho nhiều hơn một đứa trẻ. ví dụ(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
đang Sạch

3
Tôi khá chắc chắn rằng nó hoạt động cho nhiều hơn một đứa trẻ. Tôi thậm chí đã thử nó một lần nữa.
Fandi Susanto

@Fandi Susanto, Cảm ơn nó rất nhiều giúp tôi.
Dogan Ozer

9

Cách tiếp cận tốt nhất mà tôi nghĩ ra là

  1. Sử dụng dòng dõi để lưu trữ \ sort \ theo dõi cây. Thế là quá đủ, và đọc nhanh hơn hàng ngàn lần so với bất kỳ phương pháp nào khác. Nó cũng cho phép duy trì trên mẫu đó ngay cả khi DB sẽ thay đổi (vì BẤT K db db sẽ cho phép mẫu đó được sử dụng)
  2. Sử dụng chức năng xác định dòng dõi cho ID cụ thể.
  3. Sử dụng nó như bạn muốn (trong các lựa chọn, hoặc trên các hoạt động CUD, hoặc thậm chí theo công việc).

Cách tiếp cận descr. có thể tìm thấy bất cứ nơi nào, ví dụ ở đây hoặc ở đây . Về chức năng - đó là những gì khiến tôi bận tâm.

Cuối cùng - đã có ít nhiều giải pháp đơn giản, tương đối nhanh và SIMPLE.

Cơ thể chức năng

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

Và sau đó bạn chỉ

select get_lineage(the_id)

Hy vọng nó sẽ giúp được ai đó :)


9

Đã làm điều tương tự cho một câu hỏi khác ở đây

Mysql chọn đệ quy nhận tất cả con với nhiều cấp độ

Truy vấn sẽ là:

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;

Làm thế nào chúng ta có thể làm điều này? 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; Tôi không thể giới thiệu F1.idFolder cho @pv
Rahul

Tôi đã tạo lại bảng từ câu hỏi ban đầu của OP với dữ liệu như được hiển thị trong nhận xét của họ, sau đó chạy truy vấn của bạn ở đây và nhận được NULLmột kết quả duy nhất . Bạn có biết tại sao điều đó có thể? Có những điều kiện tiên quyết về mặt công cụ cơ sở dữ liệu, hoặc có điều gì đó đã thay đổi kể từ khi bạn đưa ra câu trả lời này khiến cho truy vấn này trở nên lỗi thời?
Ninja kỹ thuật số

7

Nếu bạn cần tốc độ đọc nhanh, tùy chọn tốt nhất là sử dụng bảng đóng. Một bảng đóng chứa một hàng cho mỗi cặp tổ tiên / hậu duệ. Vì vậy, trong ví dụ của bạn, bảng đóng cửa sẽ trông giống như

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

Khi bạn có bảng này, các truy vấn phân cấp trở nên rất dễ dàng và nhanh chóng. Để có được tất cả các hậu duệ của loại 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

Tất nhiên, có một nhược điểm lớn bất cứ khi nào bạn sử dụng dữ liệu không chuẩn hóa như thế này. Bạn cần duy trì bảng đóng cùng với bảng danh mục của bạn. Cách tốt nhất có lẽ là sử dụng các kích hoạt, nhưng nó hơi phức tạp để theo dõi chính xác các chèn / cập nhật / xóa cho các bảng đóng. Như với bất cứ điều gì, bạn cần xem xét các yêu cầu của bạn và quyết định phương pháp nào là tốt nhất cho bạn.

Chỉnh sửa : Xem câu hỏi Các tùy chọn để lưu trữ dữ liệu phân cấp trong cơ sở dữ liệu quan hệ là gì? để có thêm lựa chọn. Có các giải pháp tối ưu khác nhau cho các tình huống khác nhau.


4

Truy vấn đơn giản để liệt kê đệ quy đầu tiên của con:

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

Kết quả:

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

... với sự tham gia trái:

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

Giải pháp của @tincot để liệt kê tất cả trẻ em:

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)

Kiểm tra nó trực tuyến với Sql Fiddle và xem tất cả các kết quả.

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


3

Bạn có thể làm điều đó như thế này trong các cơ sở dữ liệu khác khá dễ dàng với truy vấn đệ quy (YMMV về hiệu suất).

Cách khác để làm điều đó là lưu trữ hai bit dữ liệu bổ sung, giá trị bên trái và bên phải. Giá trị bên trái và bên phải được lấy từ một giao dịch đặt hàng trước của cấu trúc cây mà bạn đại diện.

Điều này được gọi là Traversal Preorder Tree Traversal và cho phép bạn chạy một truy vấn đơn giản để nhận tất cả các giá trị cha cùng một lúc. Nó cũng có tên là "tập hợp lồng".


Tôi muốn thêm một nhận xét tương tự cho bạn, nhưng vì bạn đã làm nó, tôi sẽ chỉ thêm một liên kết đến một ví dụ hay về "tập hợp lồng nhau": mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka

2

Chỉ cần sử dụng lớp php BlueM / cây để tạo cây của bảng tự quan hệ trong mysql.

Tree và Tree \ Node là các lớp PHP để xử lý dữ liệu được cấu trúc phân cấp bằng cách sử dụng các tham chiếu ID cha. Một ví dụ điển hình là một bảng trong cơ sở dữ liệu quan hệ nơi mỗi trường mẹ cha của bản ghi tham chiếu khóa chính của bản ghi khác. Tất nhiên, Tree không thể chỉ sử dụng dữ liệu có nguồn gốc từ cơ sở dữ liệu, nhưng bất cứ điều gì: bạn cung cấp dữ liệu và Tree sử dụng dữ liệu đó, bất kể dữ liệu đến từ đâu và cách xử lý. đọc thêm

Dưới đây là một ví dụ về việc sử dụng BlueM / cây:

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

nhập mô tả hình ảnh ở đây

Đó là một bảng danh mục .

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)

Đầu ra :: nhập mô tả hình ảnh ở đây


1
Bạn có thể giải thích điều này? Nhưng tôi đảm bảo điều này đang hoạt động. Cảm ơn bạn.
wobsoriano

1
Xin vui lòng giải thích các truy vấn và ý nghĩa của @pv ?? Làm thế nào vòng lặp đang làm việc trong truy vấn này?
Amanjot Kaur

2
Dường như không hoạt động ở tất cả các cấp nếu có những đứa trẻ có ID thấp hơn cha mẹ chúng. :(
Jonas

1
@Jonas mất 20 phút để xác định vấn đề thực tế, thử với sự kết hợp khác nhau. Vâng bạn đã đúng. Nó sẽ không hoạt động với ID thấp hơn ID gốc của nó. Bạn có giải pháp nào không?
muaaz

@muaaz Cuối cùng tôi đã giải quyết nó bằng cách sử dụng trường "đường dẫn" có chứa đường dẫn cho hàng tương ứng, ví dụ: hàng có ID 577 có đường dẫn "/ 1/2/45/57 /". Nếu bạn đang tìm kiếm tất cả con của ID 2, bạn chỉ cần chọn tất cả các hàng có đường dẫn THÍCH "/ 1/2 /%". Chỉ có nhược điểm là bạn phải cập nhật các đường dẫn trong phương thức cập nhật của mình. Nhưng đối với MySQL 5.6 (tương thích), đó là giải pháp duy nhất phù hợp với tôi.
Jonas

1

Đó là một mẹo nhỏ, hãy kiểm tra xem nó có hiệu quả với bạn không

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,%';

Liên kết fiddle SQL http://www.sqlfiddle.com/#!2/e3cdf/2

Thay thế bằng tên trường và bảng của bạn một cách thích hợp.


Nó sẽ không hoạt động trong trường hợp này sqlfiddle.com/#!2/19360/2 , với thủ thuật này, ít nhất bạn nên đặt hàng theo cấp bậc trước.
Jaugar Chang

1

Một cái gì đó không được đề cập ở đây, mặc dù hơi giống với câu trả lời thứ hai của câu trả lời được chấp nhận nhưng khác nhau và chi phí thấp cho truy vấn phân cấp lớn và các mục dễ dàng (chèn xóa cập nhật), sẽ thêm một cột đường dẫn liên tục cho mỗi mục.

một số như:

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

Thí dụ:

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

Tối ưu hóa độ dài đường dẫn và ORDER BY pathsử dụng mã hóa base36 thay vì id đường dẫn số thực

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

Việc loại bỏ dấu phân cách dấu gạch chéo '/' bằng cách sử dụng độ dài và phần đệm cố định cho id được mã hóa

Giải thích tối ưu hóa chi tiết tại đây: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-m vật liệu-path /

LÀM

xây dựng một chức năng hoặc thủ tục để phân chia đường dẫn để truy xuất tổ tiên của một mục


Cảm ơn! Thú vị vớibase36
Vlad

0

Điều này làm việc cho tôi, hy vọng điều này sẽ làm việc cho bạn quá. Nó sẽ cung cấp cho bạn một bản ghi được thiết lập Root to Child cho bất kỳ Menu cụ thể nào. Thay đổi tên trường theo yêu cầu của bạn.

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;

Dường như không hoạt động ở tất cả các cấp nếu có những đứa trẻ có ID lớn hơn cha mẹ chúng
muaaz

-1

Tôi thấy nó dễ dàng hơn để:

1) tạo một hàm sẽ kiểm tra xem một mục có ở bất kỳ vị trí nào trong hệ thống phân cấp cha của một mục khác không. Một cái gì đó như thế này (tôi sẽ không viết hàm, làm cho nó bằng WHILE DO):

is_related(id, parent_id);

trong ví dụ của bạn

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

2) sử dụng một lựa chọn phụ, một cái gì đó như thế này:

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

Tôi đã thực hiện một truy vấn cho bạn. Điều này sẽ cung cấp cho bạn Danh mục đệ quy với một truy vấn duy nhất:

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

Đây là một câu đố .


Vui lòng xóa / chỉnh sửa câu trả lời của bạn để lấy lại danh tiếng tích cực của bạn.
fWd82
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.