Der er mange måder. Her er en tilgang, som jeg kan lide (og bruger regelmæssigt).
Databasen
Overvej følgende databasestruktur:
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)
);
dine data vil se sådan ud:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
Det er ret nemt at vælge alt på en brugbar måde:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
bestilling efter parent_path, date_posted
vil normalt producere resultater i den rækkefølge, du har brug for dem, når du genererer din side; men du vil gerne være sikker på, at du har et indeks i kommentartabellen, der korrekt understøtter dette -- ellers fungerer forespørgslen, men den er virkelig, virkelig ineffektiv:
create index comments_hier_idx on comments (parent_path, date_posted);
For en given enkelt kommentar er det nemt at få hele denne kommentars træ af underordnede kommentarer. Du skal blot tilføje en where-klausul:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
den tilføjede where-klausul vil gøre brug af det samme indeks, som vi allerede har defineret, så vi er godt i gang.
Bemærk, at vi ikke har brugt parent_id
endnu. Faktisk er det ikke strengt nødvendigt. Men jeg inkluderer det, fordi det giver os mulighed for at definere en traditionel fremmednøgle for at håndhæve referentiel integritet og implementere cascading sletninger og opdateringer, hvis vi ønsker det. Fremmednøglebegrænsninger og kaskaderegler er kun tilgængelige i INNODB-tabeller:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Administration af hierarkiet
For at bruge denne tilgang skal du selvfølgelig sørge for at indstille parent_path
korrekt, når du indsætter hver kommentar. Og hvis du flytter kommentarer rundt (hvilket ganske vist ville være en mærkelig usecase), skal du sørge for manuelt at opdatere hver parent_path for hver kommentar, der er underordnet den flyttede kommentar. ... men det er begge ret nemme ting at holde trit med.
Hvis du virkelig ønsker at blive fancy (og hvis din db understøtter det), kan du skrive triggere for at administrere parent_path transparent -- jeg vil efterlade dette en øvelse til læseren, men den grundlæggende idé er, at indsæt og opdaterings triggere ville blive udløst før en ny indsættelse begås. de ville gå op i træet (ved at bruge parent_id
). udenlandsk nøglerelation), og genopbygg værdien af parent_path
tilsvarende.
Det er endda muligt at bryde parent_path
ud i en separat tabel, der styres udelukkende af triggere på kommentartabellen, med nogle få visninger eller lagrede procedurer til at implementere de forskellige forespørgsler, du har brug for. På den måde isolerer du fuldstændig din mellemlagskode fra behovet for at kende til eller bekymre sig om mekanikken ved lagring af hierarkioplysninger.
Selvfølgelig er ingen af de smarte ting påkrævet på nogen måde -- det er normalt ganske tilstrækkeligt bare at droppe parent_path i tabellen og skrive noget kode i dit mellemlag for at sikre, at det bliver administreret korrekt sammen med alle de andre felter du skal allerede klare dig.
Pålægge grænser
MySQL (og nogle andre databaser) giver dig mulighed for at vælge "sider" med data ved hjælp af LIMIT
klausul:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Desværre vil LIMIT-klausulen alene ikke give de ønskede resultater, når man beskæftiger sig med hierarkiske data som denne.
-- 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;
I stedet skal vi så en separat udvælgelse på det niveau, hvor vi ønsker at pålægge grænsen, så samler vi det tilbage sammen med vores "sub-tree" forespørgsel for at give de endelige ønskede resultater.
Noget som dette:
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;
Bemærk sætningen limit 25 offset 0
, begravet i midten af det indre udvalg. Denne erklæring vil hente de seneste 25 "root-level" kommentarer.
[edit:du kan opleve, at du skal lege lidt med ting for at få muligheden for at bestille og/eller begrænse tingene præcis, som du vil. dette kan omfatte tilføjelse af information inden for hierarkiet, der er kodet i parent_path
. for eksempel:i stedet for /{id}/{id2}/{id3}/
, kan du beslutte at inkludere post_date som en del af parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Dette ville gøre det meget nemt at få den rækkefølge og hierarki, du ønsker, på bekostning af at skulle udfylde feltet på forhånd og administrere det, efterhånden som dataene ændres]
håber dette hjælper. held og lykke!