Sekarang MySQL 8.0 mendukung kueri rekursif , kita dapat mengatakan bahwa semua database SQL populer mendukung kueri rekursif dalam sintaksis standar.
WITH RECURSIVE MyTree AS (
SELECT * FROM MyTable WHERE ParentId IS NULL
UNION ALL
SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;
Saya menguji permintaan rekursif di MySQL 8.0 di presentasi saya Recursive Query Throwdown pada 2017.
Di bawah ini adalah jawaban asli saya dari 2008:
Ada beberapa cara untuk menyimpan data terstruktur pohon dalam database relasional. Apa yang Anda perlihatkan dalam contoh Anda menggunakan dua metode:
- Daftar Adjacency (kolom "induk") dan
- Enumerasi Jalur (angka-titik bertitik di kolom nama Anda).
Solusi lain disebut Nested Sets , dan dapat disimpan dalam tabel yang sama juga. Baca " Pohon dan Hirarki dalam SQL untuk Smarties " oleh Joe Celko untuk informasi lebih lanjut tentang desain ini.
Saya biasanya lebih suka desain yang disebut Tabel Penutupan (alias "Hubungan Adjacency") untuk menyimpan data terstruktur pohon. Membutuhkan tabel lain, tetapi kemudian permintaan pohon cukup mudah.
Saya membahas Tabel Penutupan dalam Model presentasi saya untuk Data Hirarki dengan SQL dan PHP dan dalam buku saya SQL Antipatterns: Menghindari Jebakan Pemrograman Basis Data .
CREATE TABLE ClosureTable (
ancestor_id INT NOT NULL REFERENCES FlatTable(id),
descendant_id INT NOT NULL REFERENCES FlatTable(id),
PRIMARY KEY (ancestor_id, descendant_id)
);
Simpan semua jalur di Tabel Penutupan, di mana ada keturunan langsung dari satu simpul ke simpul lainnya. Sertakan baris untuk setiap node untuk referensi itu sendiri. Misalnya, menggunakan kumpulan data yang Anda tunjukkan dalam pertanyaan Anda:
INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
(1,1), (1,2), (1,4), (1,6),
(2,2), (2,4),
(3,3), (3,5),
(4,4),
(5,5),
(6,6);
Sekarang Anda bisa mendapatkan pohon mulai dari simpul 1 seperti ini:
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
Output (dalam klien MySQL) terlihat seperti berikut:
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
Dengan kata lain, simpul 3 dan 5 dikecualikan, karena mereka merupakan bagian dari hierarki yang terpisah, bukan turun dari simpul 1.
Re: komentar dari e-satis tentang anak langsung (atau orang tua langsung). Anda dapat menambahkan path_length
kolom " " ke ClosureTable
untuk membuatnya lebih mudah untuk meminta secara khusus untuk anak atau orang tua langsung (atau jarak lainnya).
INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
(1,1,0), (1,2,1), (1,4,2), (1,6,1),
(2,2,0), (2,4,1),
(3,3,0), (3,5,1),
(4,4,0),
(5,5,0),
(6,6,0);
Kemudian Anda dapat menambahkan istilah dalam pencarian Anda untuk menanyakan langsung anak-anak dari simpul yang diberikan. Ini adalah keturunan yang path_length
1.
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
AND path_length = 1;
+----+
| id |
+----+
| 2 |
| 6 |
+----+
Komentar ulang dari @ashraf: "Bagaimana kalau menyortir seluruh pohon [dengan nama]?"
Berikut ini contoh kueri untuk mengembalikan semua node yang merupakan turunan dari simpul 1, bergabung dengan mereka ke FlatTable yang berisi atribut simpul lainnya seperti name
, dan urutkan berdasarkan namanya.
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
Komentar ulang dari @Nate:
SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id)
WHERE a.ancestor_id = 1
GROUP BY a.descendant_id
ORDER BY f.name
+------------+-------------+
| name | breadcrumbs |
+------------+-------------+
| Node 1 | 1 |
| Node 1.1 | 1,2 |
| Node 1.1.1 | 1,2,4 |
| Node 1.2 | 1,6 |
+------------+-------------+
Seorang pengguna menyarankan suntingan hari ini. SO moderator menyetujui hasil edit, tetapi saya membalikkannya.
Hasil edit menyarankan agar ORDER BY dalam kueri terakhir di atas seharusnya ORDER BY b.path_length, f.name
, mungkin untuk memastikan pemesanan sesuai dengan hierarki. Tetapi ini tidak berhasil, karena akan memesan "Node 1.1.1" setelah "Node 1.2".
Jika Anda ingin pemesanan mencocokkan hierarki dengan cara yang masuk akal, itu mungkin, tetapi tidak hanya dengan memesan berdasarkan panjang jalur. Sebagai contoh, lihat jawaban saya ke database hierarkis Tabel Penutupan MySQL - Cara mengeluarkan informasi dalam urutan yang benar .