Cara membuat kueri rekursif hierarki MySQL

271

Saya memiliki tabel MySQL yaitu sebagai berikut:

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

Sekarang, saya ingin memiliki satu permintaan MySQL yang saya sediakan dengan mudah [misalnya mengatakan 'id = 19'] maka saya harus mendapatkan semua id anak-anaknya [yaitu hasil harus memiliki id '20, 21,22 ']. ... Juga, hierarki anak-anak tidak diketahui itu dapat bervariasi ....

Juga, saya sudah memiliki solusi menggunakan for loop ..... Biarkan saya tahu bagaimana mencapai hal yang sama menggunakan permintaan MySQL tunggal jika memungkinkan.

Tarun Parswani
sumber
Misalkan hirarki adalah 7 level. Seperti apa tabel output yang Anda inginkan?
Jonathan Leffler
1
MySQL (masih) tidak mendukung permintaan hierarkis (seperti halnya DBMS modern lainnya). Anda harus menulis prosedur tersimpan atau menggunakan model data yang berbeda.
a_horse_with_no_name
1
MYSQL 8.0 akan mendukung permintaan Rekursif menggunakan CTE (Common Table Expressions)
user3712320
Bagaimana dengan mendapatkan daftar lengkap posting mulai dari id komentar terakhir? Atau anak terakhir?
joe

Jawaban:

393

Untuk MySQL 8+: gunakan withsintaks rekursif .
Untuk MySQL 5.x: gunakan variabel sebaris, ID jalur, atau gabung-sendiri.

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;

Nilai yang ditentukan dalam parent_id = 19harus ditetapkan ke iddari induk yang ingin Anda pilih semua keturunan.

MySQL 5.x

Untuk versi MySQL yang tidak mendukung Common Table Expressions (hingga versi 5.7), Anda akan mencapai ini dengan kueri berikut:

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

Ini biola .

Di sini, nilai yang ditentukan dalam @pv := '19'harus ditetapkan ke idorang tua yang ingin Anda pilih semua keturunan.

Ini akan berfungsi juga jika orang tua memiliki beberapa anak. Namun, setiap catatan harus memenuhi persyaratan parent_id < id, jika tidak hasilnya tidak akan lengkap.

Tugas variabel di dalam kueri

Kueri ini menggunakan sintaks MySQL spesifik: variabel ditugaskan dan dimodifikasi selama eksekusi. Beberapa asumsi dibuat tentang urutan eksekusi:

  • The fromklausul dievaluasi pertama. Jadi disitulah awal @pvdiinisialisasi.
  • The whereklausul dievaluasi untuk setiap record dalam urutan pengambilan dari fromalias. Jadi disinilah syarat hanya memasukkan catatan-catatan yang induknya sudah diidentifikasi berada di pohon turunan (semua keturunan dari induk primer ditambahkan secara progresif @pv).
  • Kondisi dalam whereklausul ini dievaluasi secara berurutan, dan evaluasi terputus begitu hasil totalnya pasti. Oleh karena itu kondisi kedua harus di tempat kedua, karena menambahkan daftar idke induk, dan ini hanya akan terjadi jika idmelewati kondisi pertama. The lengthFungsi hanya menelepon untuk memastikan kondisi ini selalu benar, bahkan jika pvstring yang akan untuk beberapa alasan menghasilkan nilai falsy.

Secara keseluruhan, orang mungkin menganggap asumsi ini terlalu berisiko untuk diandalkan. The dokumentasi memperingatkan:

Anda mungkin mendapatkan hasil yang Anda harapkan, tetapi ini tidak dijamin [...] urutan evaluasi untuk ekspresi yang melibatkan variabel pengguna tidak ditentukan.

Jadi meskipun itu bekerja secara konsisten dengan kueri di atas, urutan evaluasi masih dapat berubah, misalnya ketika Anda menambahkan kondisi atau menggunakan kueri ini sebagai tampilan atau sub-kueri dalam kueri yang lebih besar. Ini adalah "fitur" yang akan dihapus dalam rilis MySQL di masa mendatang :

Rilis MySQL sebelumnya memungkinkan untuk menetapkan nilai ke variabel pengguna dalam pernyataan selain SET. Fungsionalitas ini didukung dalam MySQL 8.0 untuk kompatibilitas ke belakang tetapi dapat dihapus dalam rilis MySQL berikutnya.

Seperti yang dinyatakan di atas, dari MySQL 8.0 dan seterusnya Anda harus menggunakan withsintaks rekursif .

Efisiensi

Untuk kumpulan data yang sangat besar, solusi ini mungkin lambat, karena find_in_setoperasi bukan cara yang paling ideal untuk menemukan nomor dalam daftar, tentu saja tidak dalam daftar yang mencapai ukuran dalam urutan besarnya yang sama dengan jumlah catatan yang dikembalikan.

Alternatif 1: with recursive,connect by

Semakin banyak basis data mengimplementasikan SQL: 1999 WITH [RECURSIVE]sintaks standar ISO untuk kueri rekursif (mis. Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). Dan pada versi 8.0, MySQL juga mendukungnya . Lihat bagian atas jawaban ini untuk sintaks yang digunakan.

Beberapa database memiliki alternatif, sintaksis non-standar untuk pencarian hirarkis, seperti CONNECT BYklausa yang tersedia pada Oracle , DB2 , Informix , CUBRID dan database lainnya.

MySQL versi 5.7 tidak menawarkan fitur seperti itu. Ketika mesin database Anda menyediakan sintaks ini atau Anda dapat bermigrasi ke yang tidak, maka itu tentu saja merupakan pilihan terbaik. Jika tidak, maka pertimbangkan juga alternatif berikut.

Alternatif 2: Identifier Path-style

Banyak hal menjadi jauh lebih mudah jika Anda akan menetapkan idnilai yang berisi informasi hierarkis: jalan. Misalnya, dalam kasus Anda ini bisa terlihat seperti ini:

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

Maka Anda selectakan terlihat seperti ini:

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

Alternatif 3: Gabung-Sendiri Berulang

Jika Anda tahu batas atas untuk seberapa dalam hierarki hierarki Anda, Anda dapat menggunakan sqlkueri standar seperti ini:

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;

Lihat biola ini

The wheremenspesifikasikan kondisi yang orang tua Anda ingin mengambil keturunan. Anda dapat memperluas kueri ini dengan lebih banyak level sesuai kebutuhan.

trincot
sumber
26
Saya suka penjelasan Anda. Itu tidak hanya memberikan jawaban, itu menjelaskan mengapa itu memecahkan masalah sehingga kita dapat benar-benar belajar darinya. SUNTING: juga bagus karena tidak mengandalkan mengetahui jumlah level sebelumnya.
Byson
1
@Bison, saya sangat menghargai umpan balik Anda. Terima kasih!
trincot
2
@ Avion, ini bukan sesuatu yang harus Anda letakkan di suatu tempat, itu adalah persyaratan bahwa untuk semua catatan kondisi ini benar. Jika Anda memiliki satu atau lebih catatan di mana parent_id > idmaka Anda tidak dapat menggunakan solusi ini.
trincot
2
Bagi siapa pun yang ingin menggunakan WITH RECURSIVEmetode ini, saya menemukan artikel berikut ini sangat membantu dengan berbagai skenario seperti kedalaman rekursi, perbedaan, dan mendeteksi dan menutup siklus
Kuda
2
Saya mencoba solusi utama pada MySQL5.7 di komputer saya, di meja saya sendiri, tetapi itu tidak berhasil karena setara dengan klausa @pv: = concat (@pv, ',', id) dievaluasi menjadi false. Saya memperbaikinya dengan mengubahnya menjadi panjang (@pv: = concat (@pv, ',', id))> 0 jadi itu selalu benar.
KC Wong
82

Dari blog Mengelola Data Hirarki di MySQL

Struktur meja

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

Pertanyaan:

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

Keluaran

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

Sebagian besar pengguna pada satu waktu atau yang lain telah berurusan dengan data hierarkis dalam database SQL dan tidak diragukan lagi mengetahui bahwa pengelolaan data hierarkis bukanlah tujuan dari basis data relasional. Tabel database relasional tidak hierarkis (seperti XML), tetapi hanya daftar datar. Data hierarkis memiliki hubungan orangtua-anak yang tidak secara alami direpresentasikan dalam tabel basis data relasional. Baca lebih lajut

Rujuk blog untuk lebih jelasnya.

EDIT:

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

Keluaran:

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

Referensi: Bagaimana cara melakukan permintaan SELECT Recursive di Mysql?

Damodaran
sumber
23
Itu bagus selama tidak ada lebih dari 4 level paling banyak dalam hierarki. Jika ada level N, Anda harus tahu itu untuk membuat kueri dengan benar.
Jonathan Leffler
2
@Damodaran, Terima kasih atas balasan Anda ... Yang saya butuhkan adalah kondisi di mana jumlah anak tidak diketahui ... dan di blog yang menggunakan konsep gabung dalam bahwa hierarki harus diketahui yang tidak dalam kasus saya ... jadi beri tahu saya pandangan Anda tentang hal yang sama ... Jadi, dengan kata-kata sederhana, saya memerlukan permintaan untuk menangani level 'n' hirerachy di mana 'n' tidak diketahui .....
Tarun Parswani
1
@ user3036105: tidak mungkin untuk melakukan ini di MySQL dengan satu query SQL. MySQL tidak cukup maju untuk itu. Jika Anda benar-benar membutuhkan ini, pertimbangkan untuk memutakhirkan ke DBMS yang mendukung kueri rekursif.
a_horse_with_no_name
5
> Sebagian besar pengguna pada satu waktu atau yang lain telah berurusan dengan data hierarkis dalam database SQL dan tidak diragukan lagi mengetahui bahwa pengelolaan data hierarkis bukanlah tujuan dari basis data relasional. Mungkin maksud Anda adalah database MySQL. Basis data Oracle menangani data hierarki dan kueri dengan cukup baik.
Peter Nosko
1
"... pengelolaan data hierarkis bukanlah tujuan dari basis data relasional untuk ..." Meskipun ini mungkin bukan maksud asli dari basis data relasional, dalam data hierarki dunia nyata sangat biasa dan MySQL harus mencerminkan bagaimana orang benar-benar perlu menggunakan data mereka dalam skenario dunia nyata.
Dave L
9

Coba ini:

Definisi tabel:

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;

Baris eksperimental:

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

Prosedur tersimpan rekursif:

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 ;

Fungsi pembungkus untuk prosedur tersimpan:

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 ;

Pilih contoh:

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

Keluaran:

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

Memfilter baris dengan jalur tertentu:

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

Keluaran:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
Fandi Susanto
sumber
1
Ini tidak akan berfungsi untuk lebih dari satu anak. mis(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Bersihkan kode
3
Saya cukup yakin ini bekerja untuk lebih dari satu anak. Saya bahkan mengujinya lagi.
Fandi Susanto
@Fandi Susanto, Terima kasih banyak membantu saya.
Dogan Ozer
9

Pendekatan terbaik yang saya buat adalah

  1. Gunakan silsilah untuk menyimpan \ sort \ trace tree. Itu lebih dari cukup, dan bekerja ribuan kali lebih cepat untuk membaca daripada pendekatan lainnya. Ini juga memungkinkan untuk tetap pada pola itu bahkan jika DB akan berubah (karena APAPUN db akan memungkinkan pola itu digunakan)
  2. Gunakan fungsi yang menentukan garis keturunan untuk ID tertentu.
  3. Gunakan sesuai keinginan (dalam memilih, atau pada operasi CUD, atau bahkan oleh pekerjaan).

Deskripsi pendekatan garis keturunan. dapat ditemukan di mana saja, misalnya Di Sini atau di sini . Pada fungsi - itulah yang membuat saya takut.

Pada akhirnya - mendapat solusi yang lebih atau kurang sederhana, relatif cepat, dan SIMPLE.

Fungsi tubuh

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

Dan kemudian Anda baru saja

select get_lineage(the_id)

Semoga ini bisa membantu seseorang :)

Der Zinger
sumber
9

Melakukan hal yang sama untuk pertanyaan lain di sini

Mysql pilih rekursif buat semua anak dengan level ganda

Kueri akan:

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;
Dheerendra Kulkarni
sumber
Bagaimana kita bisa melakukan ini? 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; Saya tidak bisa merujuk F1.idFolder untuk @pv
Rahul
Saya membuat ulang tabel dari pertanyaan awal OP dengan data seperti yang ditunjukkan dalam komentar mereka, lalu menjalankan kueri Anda di sini, dan mendapatkan satu NULLsebagai hasilnya. Apakah Anda tahu mengapa itu bisa terjadi? Apakah ada prasyarat dalam hal mesin basis data, atau ada sesuatu yang berubah sejak Anda membuat jawaban ini yang membuat kueri ini usang?
Digital Ninja
7

Jika Anda membutuhkan kecepatan baca cepat, opsi terbaik adalah menggunakan tabel penutupan. Tabel penutupan berisi baris untuk setiap pasangan leluhur / keturunan. Jadi dalam contoh Anda, tabel penutupan akan terlihat seperti

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

Setelah Anda memiliki tabel ini, kueri hierarki menjadi sangat mudah dan cepat. Untuk mendapatkan semua keturunan kategori 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

Tentu saja, ada kerugian besar setiap kali Anda menggunakan data yang didenormalkan seperti ini. Anda perlu mempertahankan tabel penutupan di samping tabel kategori Anda. Cara terbaik mungkin menggunakan pemicu, tetapi agak rumit untuk melacak dengan benar sisipan / pembaruan / penghapusan untuk tabel penutupan. Seperti apa pun, Anda perlu melihat persyaratan Anda dan memutuskan pendekatan apa yang terbaik untuk Anda.

Sunting : Lihat pertanyaan Apa opsi untuk menyimpan data hierarkis dalam database relasional? untuk opsi lainnya. Ada berbagai solusi optimal untuk berbagai situasi.

Justin Howard
sumber
4

Permintaan sederhana untuk mencantumkan anak rekursi pertama:

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

Hasil:

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

... dengan gabung kiri:

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

Solusi dari @tincot untuk mendaftar semua anak:

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)

Uji secara online dengan Sql Fiddle dan lihat semua hasil.

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

lynx_74
sumber
3

Anda dapat melakukannya seperti ini di database lain dengan cukup mudah dengan kueri rekursif (YMMV pada kinerja).

Cara lain untuk melakukannya adalah dengan menyimpan dua bit data tambahan, nilai kiri dan kanan. Nilai kiri dan kanan diturunkan dari traversal pre-order dari struktur pohon yang Anda wakili.

Ini dikenal sebagai Modifikasi Preorder Tree Traversal dan memungkinkan Anda menjalankan kueri sederhana untuk mendapatkan semua nilai induk sekaligus. Itu juga pergi dengan nama "set bersarang".

Phil John
sumber
Saya ingin menambahkan komentar yang serupa dengan Anda, tetapi karena Anda melakukannya, saya hanya akan menambahkan tautan ke contoh yang bagus dari "set bersarang": mikehillyer.com/articles/manajemen-hierarchical-data-in-mysql
Miroslaw Opoka
2

Cukup gunakan BlueM / tree kelas php untuk membuat pohon dari tabel relasi diri di mysql

Tree and Tree \ Node adalah kelas PHP untuk menangani data yang disusun secara hierarkis menggunakan referensi ID induk. Contoh khas adalah tabel dalam database relasional di mana setiap bidang "induk" rekaman merujuk kunci utama dari catatan lain. Tentu saja, Tree tidak hanya dapat menggunakan data yang berasal dari database, tetapi apa pun: Anda menyediakan data, dan Tree menggunakannya, terlepas dari mana data itu berasal dan bagaimana data itu diproses. Baca lebih lajut

Berikut adalah contoh penggunaan BlueM / tree:

<?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); 
...
Saleh Mosleh
sumber
2

masukkan deskripsi gambar di sini

Ini adalah tabel kategori .

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)

Keluaran:: masukkan deskripsi gambar di sini

Pradip Rupareliya
sumber
1
Bisakah Anda menjelaskan ini? Tapi saya jamin ini berfungsi. Terima kasih.
wobsoriano
1
tlg jelaskan pertanyaannya dan apa arti @pv ?? Bagaimana loop bekerja di kueri ini?
Amanjot Kaur
2
Tampaknya tidak berfungsi di semua tingkatan jika ada anak-anak yang memiliki ID lebih rendah dari orang tua mereka. :(
Jonas
1
@Jonas membutuhkan waktu 20 menit untuk mengidentifikasi masalah yang sebenarnya, mencoba dengan kombinasi yang berbeda. ya kau benar. Itu tidak akan bekerja dengan ID lebih rendah dari ID induknya. Apakah Anda punya solusi?
muaaz
@muaaz Saya akhirnya menyelesaikannya dengan menggunakan bidang "jalur" yang berisi jalur untuk masing-masing baris, misalnya baris dengan ID 577 memiliki jalur "/ 1/2/45/577 /". Jika Anda mencari semua anak ID 2, Anda cukup memilih semua baris dengan jalur SEPERTI "/ 1/2 /%". Satunya downside adalah bahwa Anda harus memperbarui jalur dalam metode pembaruan Anda. Tetapi untuk MySQL 5.6 (kompatibel), itu adalah satu-satunya solusi yang bekerja untuk saya.
Jonas
1

Ini sedikit rumit, periksa apakah ini berfungsi untuk Anda

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

SQL fiddle link http://www.sqlfiddle.com/#!2/e3cdf/2

Ganti dengan bidang dan nama tabel Anda dengan tepat.

senK
sumber
Ini tidak akan berfungsi dalam kasus ini sqlfiddle.com/#!2/19360/2 , dengan trik ini, setidaknya Anda harus memesan berdasarkan tingkat hierarkis terlebih dahulu.
Jaugar Chang
1

Sesuatu yang tidak disebutkan di sini, meskipun sedikit mirip dengan alternatif kedua dari jawaban yang diterima tetapi berbeda dan berbiaya rendah untuk kueri hierarki besar dan item mudah (masukkan pembaruan dihapus), akan menambahkan kolom jalur persisten untuk setiap item.

beberapa seperti:

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

Contoh:

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

Optimalkan panjang jalur dan ORDER BY pathgunakan pengkodean base36 sebagai ganti jalur numerik nyata

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

Menekan juga pemisah slash '/' dengan menggunakan panjang tetap dan padding ke id yang disandikan

Penjelasan optimasi terperinci di sini: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

MELAKUKAN

membangun fungsi atau prosedur untuk membagi jalur bagi nenek moyang retreive dari satu item

MTK
sumber
Terima kasih! Menarik denganbase36
Vlad
0

Ini bekerja untuk saya, semoga ini juga bekerja untuk Anda. Ini akan memberi Anda Record set Root to Child untuk Menu Tertentu apa pun. Ubah nama Field sesuai kebutuhan Anda.

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;
Monzurum
sumber
Tampaknya tidak berfungsi di semua tingkatan jika ada anak-anak yang memiliki ID lebih besar daripada orang tua mereka
muaaz
-1

Saya merasa lebih mudah untuk:

1) membuat fungsi yang akan memeriksa apakah suatu item ada di mana saja dalam hierarki induk dari yang lain. Sesuatu seperti ini (saya tidak akan menulis fungsinya, membuatnya dengan WHILE DO):

is_related(id, parent_id);

dalam contoh Anda

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

2) gunakan sub-pilih, sesuatu seperti ini:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
cripox
sumber
-1

Saya telah membuat permintaan untuk Anda. Ini akan memberi Anda Kategori Rekursif dengan Pertanyaan Tunggal:

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

Ini biola .

Manish
sumber
Harap hapus / edit jawaban Anda untuk mendapatkan kembali reputasi positif Anda.
fWd82