Ci sono molti modi. Ecco un approccio che mi piace (e che uso regolarmente).
Il database
Considera la seguente struttura del database:
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)
);
i tuoi dati avranno questo aspetto:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
È abbastanza facile selezionare tutto in modo utilizzabile:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
ordinando per parent_path, date_posted
di solito produrrà risultati nell'ordine in cui ti serviranno quando genererai la tua pagina; ma vorrai essere sicuro di avere un indice nella tabella dei commenti che lo supporterà correttamente, altrimenti la query funziona, ma è davvero, davvero inefficiente:
create index comments_hier_idx on comments (parent_path, date_posted);
Per ogni singolo commento, è facile ottenere l'intero albero dei commenti dei bambini di quel commento. Basta aggiungere una clausola where:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
la clausola where aggiunta utilizzerà lo stesso indice che abbiamo già definito, quindi siamo a posto.
Nota che non abbiamo utilizzato il parent_id
ancora. In realtà, non è strettamente necessario. Ma lo includo perché ci consente di definire una chiave esterna tradizionale per rafforzare l'integrità referenziale e per implementare eliminazioni e aggiornamenti a cascata, se lo desideriamo. I vincoli di chiave esterna e le regole a cascata sono disponibili solo nelle tabelle INNODB:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Gestire la gerarchia
Per utilizzare questo approccio, ovviamente, dovrai assicurarti di impostare il parent_path
correttamente quando inserisci ogni commento. E se sposti i commenti (che sarebbe certamente uno strano caso d'uso), dovrai assicurarti di aggiornare manualmente ogni parent_path di ogni commento che è subordinato al commento spostato. ... ma queste sono entrambe cose abbastanza facili da tenere al passo.
Se vuoi davvero divertirti (e se il tuo db lo supporta), puoi scrivere trigger per gestire il parent_path in modo trasparente - lascerò questo un esercizio per il lettore, ma l'idea di base è che i trigger di inserimento e aggiornamento si attiverebbero prima che venga eseguito il commit di un nuovo inserto. salirebbero sull'albero (usando il parent_id
relazione chiave esterna) e ricostruire il valore di parent_path
di conseguenza.
È anche possibile interrompere il parent_path
in una tabella separata gestita interamente dai trigger nella tabella dei commenti, con alcune viste o procedure memorizzate per implementare le varie query necessarie. Isolando così completamente il codice di livello intermedio dalla necessità di conoscere o preoccuparsi dei meccanismi di archiviazione delle informazioni sulla gerarchia.
Ovviamente, nessuna delle cose stravaganti è richiesta in alcun modo:di solito è abbastanza sufficiente rilasciare il parent_path nella tabella e scrivere del codice nel tuo livello intermedio per assicurarti che venga gestito correttamente insieme a tutti gli altri campi devi già gestire.
Imporre limiti
MySQL (e alcuni altri database) ti consente di selezionare "pagine" di dati utilizzando il LIMIT
clausola:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Sfortunatamente, quando si tratta di dati gerarchici come questo, la clausola LIMIT da sola non darà i risultati desiderati.
-- 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;
Invece, abbiamo bisogno di una selezione separata al livello in cui vogliamo imporre il limite, quindi lo uniamo di nuovo alla nostra query "sottoalbero" per dare i risultati finali desiderati.
Qualcosa del genere:
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;
Notare l'istruzione limit 25 offset 0
, sepolto nel mezzo della selezione interna. Questa istruzione recupererà i 25 commenti "a livello principale" più recenti.
[modifica:potresti scoprire che devi giocare un po' con le cose per avere la possibilità di ordinare e/o limitare le cose esattamente come preferisci. ciò può includere l'aggiunta di informazioni all'interno della gerarchia codificata in parent_path
. ad esempio:invece di /{id}/{id2}/{id3}/
, potresti decidere di includere post_date come parte di parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Ciò renderebbe molto facile ottenere l'ordine e la gerarchia desiderati, a scapito di dover compilare il campo in anticipo e gestirlo quando i dati cambiano]
spero che questo aiuti.buona fortuna!