Er zijn veel manieren. Hier is een benadering die ik leuk vind (en regelmatig gebruik).
De database
Overweeg de volgende databasestructuur:
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)
);
uw gegevens zien er als volgt uit:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
Het is vrij eenvoudig om alles op een bruikbare manier te selecteren:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
bestellen via parent_path, date_posted
zal gewoonlijk resultaten opleveren in de volgorde waarin u ze nodig heeft wanneer u uw pagina genereert; maar je wilt er zeker van zijn dat je een index in de opmerkingentabel hebt die dit goed ondersteunt -- anders werkt de query, maar het is echt, echt inefficiënt:
create index comments_hier_idx on comments (parent_path, date_posted);
Voor elke afzonderlijke opmerking is het gemakkelijk om de hele boom met onderliggende opmerkingen van die opmerking te krijgen. Voeg gewoon een waar-clausule toe:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
de toegevoegde where-clausule maakt gebruik van dezelfde index die we al hebben gedefinieerd, dus we zijn klaar om te gaan.
Merk op dat we de parent_id
. niet hebben gebruikt nog. Sterker nog, het is niet strikt noodzakelijk. Maar ik voeg het toe omdat het ons in staat stelt een traditionele externe sleutel te definiëren om referentiële integriteit af te dwingen en om trapsgewijze verwijderingen en updates te implementeren als we dat willen. Externe sleutelbeperkingen en trapsgewijze regels zijn alleen beschikbaar in INNODB-tabellen:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
De hiërarchie beheren
Om deze aanpak te gebruiken, moet je er natuurlijk voor zorgen dat je het parent_path
instelt correct wanneer u elke opmerking invoegt. En als je opmerkingen verplaatst (wat weliswaar een vreemde usecase zou zijn), moet je ervoor zorgen dat je elk parent_path handmatig bijwerkt van elke opmerking die ondergeschikt is aan de verplaatste opmerking. ... maar dat zijn beide vrij gemakkelijke dingen om bij te houden.
Als je echt zin wilt krijgen (en als je db het ondersteunt), kun je triggers schrijven om het parent_path transparant te beheren - ik laat dit een oefening voor de lezer, maar het basisidee is dat triggers voor invoegen en bijwerken zouden worden geactiveerd voordat een nieuwe insert wordt vastgelegd. ze zouden de boom in lopen (met behulp van de parent_id
relatie met refererende sleutels), en herbouw de waarde van het parent_path
dienovereenkomstig.
Het is zelfs mogelijk om het parent_path
te breken uit in een aparte tabel die volledig wordt beheerd door triggers in de tabel met opmerkingen, met een paar views of opgeslagen procedures om de verschillende query's te implementeren die je nodig hebt. Zo wordt uw middle-tier code volledig geïsoleerd van de noodzaak om de mechanica van het opslaan van de hiërarchie-informatie te kennen of erom te geven.
Natuurlijk zijn geen van de mooie dingen vereist - het is meestal voldoende om gewoon het parent_path in de tabel te plaatsen en wat code in je middelste laag te schrijven om ervoor te zorgen dat het goed wordt beheerd, samen met alle andere velden je moet het al regelen.
Grenzen opleggen
Met MySQL (en sommige andere databases) kunt u "pagina's" met gegevens selecteren met behulp van de LIMIT
clausule:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Helaas levert de LIMIT-clausule alleen niet de gewenste resultaten op bij het omgaan met hiërarchische gegevens zoals deze.
-- 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;
In plaats daarvan moeten we dus een aparte selectie maken op het niveau waar we de limiet willen opleggen, dan voegen we dat weer samen met onze "subboom"-query om de uiteindelijke gewenste resultaten te geven.
Zoiets als dit:
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;
Let op de verklaring limit 25 offset 0
, begraven in het midden van de binnenste select. Deze verklaring haalt de meest recente 25 "root-level" commentaren op.
[edit:je zult misschien merken dat je een beetje met dingen moet spelen om de mogelijkheid te krijgen om dingen te ordenen en/of te beperken zoals je wilt. dit kan het toevoegen van informatie in de hiërarchie omvatten die is gecodeerd in parent_path
. bijvoorbeeld:in plaats van /{id}/{id2}/{id3}/
, kunt u besluiten om de post_date op te nemen als onderdeel van het parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Dit zou het heel gemakkelijk maken om de gewenste volgorde en hiërarchie te krijgen, ten koste van het veld vooraf moeten invullen en beheren als de gegevens veranderen]
hoop dat dit helpt.veel succes!