Il existe de nombreuses façons. Voici une approche que j'aime (et que j'utilise régulièrement).
La base de données
Considérez la structure de base de données suivante :
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)
);
vos données ressembleront à ceci :
+-----+-------------------------------------+--------------------------+---------------+
| 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...
Il est assez facile de tout sélectionner de manière utilisable :
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
classement par parent_path, date_posted
produira généralement des résultats dans l'ordre dans lequel vous en aurez besoin lorsque vous générerez votre page ; mais vous voudrez être sûr que vous avez un index sur la table des commentaires qui le supportera correctement -- sinon la requête fonctionne, mais c'est vraiment, vraiment inefficace :
create index comments_hier_idx on comments (parent_path, date_posted);
Pour un seul commentaire donné, il est facile d'obtenir l'arborescence complète des commentaires enfants de ce commentaire. Ajoutez simplement une clause 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 clause where ajoutée utilisera le même index que nous avons déjà défini, nous sommes donc prêts à partir.
Notez que nous n'avons pas utilisé le parent_id
encore. En fait, ce n'est pas strictement nécessaire. Mais je l'inclus car cela nous permet de définir une clé étrangère traditionnelle pour faire respecter l'intégrité référentielle et pour implémenter des suppressions et des mises à jour en cascade si nous le souhaitons. Les contraintes de clé étrangère et les règles en cascade ne sont disponibles que dans les tables INNODB :
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Gestion de la hiérarchie
Pour utiliser cette approche, bien sûr, vous devrez vous assurer de définir le parent_path
correctement lorsque vous insérez chaque commentaire. Et si vous déplacez des commentaires (ce qui serait certes un cas d'utilisation étrange), vous devrez vous assurer de mettre à jour manuellement chaque parent_path de chaque commentaire subordonné au commentaire déplacé. ... mais ce sont deux choses assez faciles à suivre.
Si vous voulez vraiment être fantaisiste (et si votre base de données le prend en charge), vous pouvez écrire des déclencheurs pour gérer le parent_path de manière transparente - je laisserai cet exercice au lecteur, mais l'idée de base est que les déclencheurs d'insertion et de mise à jour se déclencheraient avant qu'une nouvelle insertion ne soit validée. ils remonteraient l'arbre (en utilisant le parent_id
relation de clé étrangère), et reconstruire la valeur du parent_path
en conséquence.
Il est même possible de casser le parent_path
dans une table distincte entièrement gérée par des déclencheurs sur la table des commentaires, avec quelques vues ou procédures stockées pour implémenter les différentes requêtes dont vous avez besoin. Isolant ainsi complètement votre code de niveau intermédiaire du besoin de connaître ou de se soucier des mécanismes de stockage des informations de la hiérarchie.
Bien sûr, aucune des choses fantaisistes n'est requise par quelque moyen que ce soit - il suffit généralement de déposer simplement le parent_path dans la table et d'écrire du code dans votre niveau intermédiaire pour s'assurer qu'il est correctement géré avec tous les autres champs il faut déjà gérer.
Imposer des limites
MySQL (et quelques autres bases de données) vous permet de sélectionner des "pages" de données en utilisant le LIMIT
clause :
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Malheureusement, lorsqu'il s'agit de données hiérarchiques comme celle-ci, la clause LIMIT seule ne donnera pas les résultats souhaités.
-- 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;
Au lieu de cela, nous devons donc faire une sélection séparée au niveau où nous voulons imposer la limite, puis nous rejoignons cela avec notre requête "sous-arborescence" pour donner les résultats finaux souhaités.
Quelque chose comme ça :
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;
Remarquez la déclaration limit 25 offset 0
, enterré au milieu de la sélection intérieure. Cette instruction récupérera les 25 commentaires "de niveau racine" les plus récents.
[modifier :vous constaterez peut-être que vous devez jouer un peu avec les choses pour avoir la possibilité d'ordonner et/ou de limiter les choses exactement comme vous le souhaitez. cela peut inclure l'ajout d'informations dans la hiérarchie qui sont encodées dans parent_path
. par exemple :au lieu de /{id}/{id2}/{id3}/
, vous pouvez décider d'inclure la post_date dans le parent_path :/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Cela faciliterait l'obtention de l'ordre et de la hiérarchie souhaités, au détriment de la nécessité de remplir le champ à l'avance et de le gérer au fur et à mesure que les données changent]
J'espère que cela vous aidera. Bonne chance !