Es gibt viele Wege. Hier ist ein Ansatz, den ich mag (und regelmäßig verwende).
Die Datenbank
Betrachten Sie die folgende Datenbankstruktur:
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)
);
Ihre Daten werden wie folgt aussehen:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
Es ist ziemlich einfach, alles auf brauchbare Weise auszuwählen:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
Sortieren nach parent_path, date_posted
liefert normalerweise Ergebnisse in der Reihenfolge, in der Sie sie benötigen, wenn Sie Ihre Seite erstellen; aber Sie sollten sicher sein, dass Sie einen Index für die Kommentartabelle haben, der dies richtig unterstützt -- andernfalls funktioniert die Abfrage, aber sie ist wirklich, wirklich ineffizient:
create index comments_hier_idx on comments (parent_path, date_posted);
Für jeden einzelnen Kommentar ist es einfach, den gesamten Baum der untergeordneten Kommentare dieses Kommentars abzurufen. Fügen Sie einfach eine where-Klausel hinzu:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
Die hinzugefügte where-Klausel verwendet denselben Index, den wir bereits definiert haben, also können wir loslegen.
Beachten Sie, dass wir die parent_id
nicht verwendet haben noch. Tatsächlich ist es nicht unbedingt notwendig. Aber ich schließe es ein, weil es uns erlaubt, einen traditionellen Fremdschlüssel zu definieren, um die referenzielle Integrität zu erzwingen und kaskadierende Löschungen und Aktualisierungen zu implementieren, wenn wir wollen. Fremdschlüsseleinschränkungen und Kaskadierungsregeln sind nur in INNODB-Tabellen verfügbar:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Verwalten der Hierarchie
Um diesen Ansatz zu verwenden, müssen Sie natürlich sicherstellen, dass Sie den parent_path
festlegen richtig, wenn Sie jeden Kommentar einfügen. Und wenn Sie Kommentare verschieben (was zugegebenermaßen ein seltsamer Anwendungsfall wäre), müssen Sie sicherstellen, dass Sie jeden parent_path jedes Kommentars, der dem verschobenen Kommentar untergeordnet ist, manuell aktualisieren. ... aber das sind beide ziemlich einfache Dinge, mit denen man Schritt halten kann.
Wenn Sie wirklich ausgefallen sein möchten (und wenn Ihre Datenbank dies unterstützt), können Sie Trigger schreiben, um den parent_path transparent zu verwalten -- ich überlasse dies als Übung für den Leser, aber die Grundidee ist, dass Insert- und Update-Trigger ausgelöst werden bevor eine neue Einfügung festgeschrieben wird. sie würden den Baum hochgehen (unter Verwendung der parent_id
Fremdschlüsselbeziehung) und erstellen Sie den Wert von parent_path
neu entsprechend.
Es ist sogar möglich, den parent_path
zu brechen in eine separate Tabelle, die vollständig von Triggern in der Kommentartabelle verwaltet wird, mit einigen Ansichten oder gespeicherten Prozeduren, um die verschiedenen Abfragen zu implementieren, die Sie benötigen. Dadurch wird Ihr Code der mittleren Ebene vollständig von der Notwendigkeit isoliert, die Mechanismen zum Speichern der Hierarchieinformationen zu kennen oder sich darum zu kümmern.
Natürlich ist keines der ausgefallenen Dinge erforderlich – es reicht normalerweise völlig aus, parent_path einfach in die Tabelle zu ziehen und etwas Code in Ihre Middle-Tier zu schreiben, um sicherzustellen, dass es zusammen mit allen anderen Feldern ordnungsgemäß verwaltet wird das musst du schon managen.
Grenzen auferlegen
MySQL (und einige andere Datenbanken) erlaubt es Ihnen, "Seiten" von Daten auszuwählen, indem Sie das LIMIT
verwenden Klausel:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Beim Umgang mit hierarchischen Daten wie dieser führt die LIMIT-Klausel allein leider nicht zu den gewünschten Ergebnissen.
-- 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;
Stattdessen müssen wir auf der Ebene, auf der wir das Limit festlegen möchten, eine separate Auswahl treffen, die wir dann wieder mit unserer "Unterbaum" -Abfrage verbinden, um die endgültigen gewünschten Ergebnisse zu erhalten.
Etwa so:
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;
Beachten Sie die Anweisung limit 25 offset 0
, begraben in der Mitte der inneren Auswahl. Diese Anweisung ruft die letzten 25 "Root-Level"-Kommentare ab.
[Bearbeiten:Sie werden vielleicht feststellen, dass Sie ein bisschen mit Sachen spielen müssen, um die Möglichkeit zu bekommen, Dinge genau so zu ordnen und/oder einzuschränken, wie Sie möchten. dies kann das Hinzufügen von Informationen innerhalb der Hierarchie umfassen, die in parent_path
codiert sind . zum Beispiel:statt /{id}/{id2}/{id3}/
, können Sie das post_date als Teil des parent_path angeben:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Dies würde es sehr einfach machen, die gewünschte Reihenfolge und Hierarchie zu erhalten, auf Kosten der Notwendigkeit, das Feld im Voraus auszufüllen und es zu verwalten, wenn sich die Daten ändern]
Ich hoffe, das hilft. Viel Glück!