Det finns många sätt. Här är ett tillvägagångssätt som jag gillar (och använder regelbundet).
Databasen
Tänk på följande databasstruktur:
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)
);
din data kommer att se ut så här:
+-----+-------------------------------------+--------------------------+---------------+
| 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 är ganska enkelt att välja allt på ett användbart sätt:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
beställning efter parent_path, date_posted
kommer vanligtvis att ge resultat i den ordning du behöver dem när du skapar din sida; men du vill vara säker på att du har ett index i kommentarstabellen som stöder detta ordentligt -- annars fungerar frågan, men den är verkligen, riktigt ineffektiv:
create index comments_hier_idx on comments (parent_path, date_posted);
För en given enskild kommentar är det lätt att få den kommentarens hela träd av underordnade kommentarer. Lägg bara till 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 tillagda where-satsen kommer att använda samma index som vi redan har definierat, så vi är igång.
Observera att vi inte har använt parent_id
än. I själva verket är det inte strikt nödvändigt. Men jag inkluderar det eftersom det tillåter oss att definiera en traditionell främmande nyckel för att upprätthålla referensintegritet och för att implementera överlappande raderingar och uppdateringar om vi vill. Främmande nyckelbegränsningar och kaskadregler är endast tillgängliga i INNODB-tabeller:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Hantera hierarkin
För att använda detta tillvägagångssätt måste du naturligtvis se till att du ställer in parent_path
korrekt när du infogar varje kommentar. Och om du flyttar runt kommentarer (vilket visserligen skulle vara ett konstigt användningsfall), måste du se till att du manuellt uppdaterar varje parent_path för varje kommentar som är underordnad den flyttade kommentaren. ... men båda är ganska lätta att hänga med.
Om du verkligen vill bli fancy (och om din db stöder det), kan du skriva triggers för att hantera parent_path transparent -- jag lämnar det här en övning för läsaren, men den grundläggande idén är att infoga och uppdatera triggers skulle aktiveras innan ett nytt inlägg görs. de skulle gå upp i trädet (med hjälp av parent_id
). främmande nyckelrelation), och bygg om värdet för parent_path
i enlighet därmed.
Det är till och med möjligt att bryta parent_path
ut i en separat tabell som helt hanteras av triggers i kommentarstabellen, med några få vyer eller lagrade procedurer för att implementera de olika frågorna du behöver. På så sätt isolerar du helt och hållet din mellanskiktskod från behovet av att känna till eller bry sig om mekaniken för att lagra hierarkiinformationen.
Naturligtvis krävs inget av de tjusiga sakerna på något sätt -- det är vanligtvis ganska tillräckligt att bara släppa parent_path i tabellen och skriva lite kod i din mellannivå för att säkerställa att den hanteras korrekt tillsammans med alla andra fält du måste redan klara dig.
Införande av gränser
MySQL (och vissa andra databaser) låter dig välja "sidor" med data med hjälp av LIMIT
klausul:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Tyvärr, när man hanterar hierarkiska data som denna, kommer LIMIT-satsen ensam inte att ge det önskade resultatet.
-- 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;
Istället måste vi göra ett separat val på den nivå där vi vill införa gränsen, sedan kopplar vi tillbaka det tillsammans med vår "sub-tree"-fråga för att ge det slutliga önskade resultatet.
Något så här:
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;
Lägg märke till uttalandet limit 25 offset 0
, begravd i mitten av den inre välja. Detta uttalande kommer att hämta de senaste 25 "root-level" kommentarerna.
[edit:du kanske upptäcker att du måste leka med saker lite för att få möjligheten att beställa och/eller begränsa saker precis som du vill. detta kan innefatta att lägga till information inom hierarkin som är kodad i parent_path
. till exempel:istället för /{id}/{id2}/{id3}/
, kan du välja att inkludera post_date som en del av parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Detta skulle göra det mycket enkelt att få den ordning och hierarki du vill ha, på bekostnad av att du måste fylla i fältet i förväg och hantera det när data ändras]
hoppas detta hjälper. lycka till!