Ada banyak cara. Inilah salah satu pendekatan yang saya sukai (dan gunakan secara teratur).
Basis data
Perhatikan struktur database berikut:
CREATE TABLE comments (
id int(11) unsigned NOT NULL auto_increment,
parent_id int(11) unsigned default NULL,
parent_path varchar(255) NOT NULL,
comment_text varchar(255) NOT NULL,
date_posted datetime NOT NULL,
PRIMARY KEY (id)
);
data Anda akan terlihat seperti ini:
+-----+-------------------------------------+--------------------------+---------------+
| id | parent_id | parent_path | comment_text | date_posted |
+-----+-------------------------------------+--------------------------+---------------+
| 1 | null | / | I'm first | 1288464193 |
| 2 | 1 | /1/ | 1st Reply to I'm First | 1288464463 |
| 3 | null | / | Well I'm next | 1288464331 |
| 4 | null | / | Oh yeah, well I'm 3rd | 1288464361 |
| 5 | 3 | /3/ | reply to I'm next | 1288464566 |
| 6 | 2 | /1/2/ | this is a 2nd level reply| 1288464193 |
... and so on...
Cukup mudah untuk memilih semuanya dengan cara yang bisa digunakan:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
memesan dengan parent_path, date_posted
biasanya akan memberikan hasil sesuai urutan yang Anda perlukan saat membuat halaman; tetapi Anda ingin memastikan bahwa Anda memiliki indeks pada tabel komentar yang akan mendukung hal ini dengan baik -- jika tidak, kueri berfungsi, tetapi benar-benar tidak efisien:
create index comments_hier_idx on comments (parent_path, date_posted);
Untuk setiap komentar tunggal yang diberikan, mudah untuk mendapatkan seluruh pohon komentar anak dari komentar itu. Cukup tambahkan klausa where:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
klausa where yang ditambahkan akan menggunakan indeks yang sama yang telah kita definisikan, jadi kita siap melakukannya.
Perhatikan bahwa kami belum menggunakan parent_id
belum. Sebenarnya, itu tidak sepenuhnya diperlukan. Tapi saya memasukkannya karena memungkinkan kita untuk mendefinisikan kunci asing tradisional untuk menegakkan integritas referensial dan untuk mengimplementasikan penghapusan dan pembaruan berjenjang jika kita mau. Batasan kunci asing dan aturan cascading hanya tersedia di tabel INNODB:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Mengelola Hirarki
Untuk menggunakan pendekatan ini, tentu saja, Anda harus memastikan bahwa Anda menyetel parent_path
dengan benar ketika Anda memasukkan setiap komentar. Dan jika Anda memindahkan komentar (yang memang merupakan kasus penggunaan yang aneh), Anda harus memastikan bahwa Anda secara manual memperbarui setiap parent_path dari setiap komentar yang berada di bawah komentar yang dipindahkan. ... tetapi keduanya adalah hal yang cukup mudah untuk diikuti.
Jika Anda benar-benar ingin menjadi mewah (dan jika db Anda mendukungnya), Anda dapat menulis pemicu untuk mengelola parent_path secara transparan -- Saya akan membiarkan ini sebagai latihan untuk pembaca, tetapi ide dasarnya adalah bahwa pemicu penyisipan dan pembaruan akan diaktifkan sebelum insert baru dilakukan. mereka akan berjalan ke atas pohon (menggunakan parent_id
hubungan kunci asing), dan membangun kembali nilai parent_path
sesuai.
Bahkan dimungkinkan untuk memecahkan parent_path
keluar ke tabel terpisah yang dikelola sepenuhnya oleh pemicu di tabel komentar, dengan beberapa tampilan atau prosedur tersimpan untuk menerapkan berbagai kueri yang Anda butuhkan. Jadi benar-benar mengisolasi kode tingkat menengah Anda dari kebutuhan untuk mengetahui atau peduli tentang mekanisme penyimpanan info hierarki.
Tentu saja, tidak ada barang mewah yang diperlukan dengan cara apa pun -- biasanya cukup dengan memasukkan parent_path ke dalam tabel, dan menulis beberapa kode di tingkat menengah Anda untuk memastikan bahwa kode tersebut dikelola dengan baik bersama dengan semua bidang lainnya Anda sudah harus mengelola.
Memberlakukan batasan
MySQL (dan beberapa database lainnya) memungkinkan Anda untuk memilih "halaman" data menggunakan LIMIT
klausa:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Sayangnya, ketika berurusan dengan data hierarkis seperti ini, klausa LIMIT saja tidak akan memberikan hasil yang diinginkan.
-- the following will NOT work as intended
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted
LIMIT 25 OFFSET 0;
Sebagai gantinya, kita perlu memilih secara terpisah pada tingkat di mana kita ingin menerapkan batas, lalu kita menggabungkannya kembali dengan kueri "sub-pohon" kita untuk memberikan hasil akhir yang diinginkan.
Sesuatu seperti ini:
select
a.*
from
comments a join
(select id, parent_path
from comments
where parent_id is null
order by parent_path, post_date DESC
limit 25 offset 0) roots
on a.parent_path like concat(roots.parent_path,roots.id,'/%') or a.id=roots.id)
order by a.parent_path , post_date DESC;
Perhatikan pernyataan limit 25 offset 0
, terkubur di tengah pilih dalam. Pernyataan ini akan mengambil 25 komentar "tingkat akar" terbaru.
[Sunting:Anda mungkin menemukan bahwa Anda harus bermain dengan hal-hal sedikit untuk mendapatkan kemampuan untuk memesan dan/atau membatasi hal-hal persis seperti yang Anda suka. ini mungkin termasuk menambahkan informasi dalam hierarki yang dikodekan dalam parent_path
. misalnya:alih-alih /{id}/{id2}/{id3}/
, Anda dapat memutuskan untuk menyertakan post_date sebagai bagian dari parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Ini akan sangat memudahkan untuk mendapatkan urutan dan hierarki yang Anda inginkan, dengan mengorbankan keharusan mengisi kolom di awal, dan mengelolanya saat data berubah]
semoga membantu. semoga berhasil!