Jest wiele sposobów. Oto jedno podejście, które lubię (i używam regularnie).
Baza danych
Rozważ następującą strukturę bazy danych:
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)
);
Twoje dane będą wyglądać tak:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
Dosyć łatwo jest wybrać wszystko w użyteczny sposób:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
zamawianie według parent_path, date_posted
zwykle generuje wyniki w kolejności, w jakiej będziesz ich potrzebować podczas generowania strony; ale będziesz chciał mieć pewność, że masz indeks w tabeli komentarzy, który prawidłowo to obsługuje -- w przeciwnym razie zapytanie działa, ale jest naprawdę, bardzo nieefektywne:
create index comments_hier_idx on comments (parent_path, date_posted);
Dla każdego pojedynczego komentarza łatwo jest uzyskać całe drzewo komentarzy podrzędnych. Po prostu dodaj klauzulę WHERE:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
dodana klauzula where będzie korzystać z tego samego indeksu, który już zdefiniowaliśmy, więc dobrze jest iść.
Zauważ, że nie użyliśmy parent_id
już. W rzeczywistości nie jest to absolutnie konieczne. Ale dołączam go, ponieważ pozwala nam zdefiniować tradycyjny klucz obcy, aby wymusić integralność referencyjną i wdrożyć kaskadowe usuwanie i aktualizacje, jeśli chcemy. Ograniczenia klucza obcego i reguły kaskadowe są dostępne tylko w tabelach INNODB:
ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Zarządzanie hierarchią
Aby skorzystać z tego podejścia, oczywiście musisz upewnić się, że ustawiłeś parent_path
prawidłowo po wstawieniu każdego komentarza. A jeśli przenosisz komentarze (co byłoby dziwnym przypadkiem użycia), musisz upewnić się, że ręcznie aktualizujesz każdą ścieżkę parent_path każdego komentarza, który jest podrzędny wobec przeniesionego komentarza. ... ale obie te rzeczy są dość łatwe, aby nadążyć.
Jeśli naprawdę chcesz być fantazyjny (i jeśli twoja baza danych to obsługuje), możesz napisać wyzwalacze, aby w przejrzysty sposób zarządzać ścieżką_rodzica -- zostawię to ćwiczenie dla czytelnika, ale podstawową ideą jest to, że wyzwalacze wstawiania i aktualizowania będą uruchamiane przed zatwierdzeniem nowego wstawiania. pójdą w górę drzewa (używając parent_id
relacji klucza obcego) i odbuduj wartość parent_path
odpowiednio.
Możliwe jest nawet złamanie parent_path
do osobnej tabeli, która jest całkowicie zarządzana przez wyzwalacze w tabeli komentarzy, z kilkoma widokami lub procedurami składowanymi w celu zaimplementowania różnych potrzebnych zapytań. W ten sposób całkowicie izolujemy kod średniego poziomu od konieczności znajomości mechaniki przechowywania informacji o hierarchii lub dbania o nią.
Oczywiście żadna z tych wymyślnych rzeczy nie jest wymagana w żaden sposób — zwykle wystarczy po prostu wrzucić parent_path do tabeli i napisać kod w warstwie pośredniej, aby upewnić się, że jest prawidłowo zarządzany wraz ze wszystkimi innymi polami już musisz sobie poradzić.
Nakładanie ograniczeń
MySQL (i niektóre inne bazy danych) pozwala wybrać "strony" danych za pomocą LIMIT
klauzula:
SELECT * FROM mytable LIMIT 25 OFFSET 0;
Niestety, w przypadku takich danych hierarchicznych, sama klauzula LIMIT nie przyniesie oczekiwanych rezultatów.
-- 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;
Zamiast tego musimy dokonać osobnego wyboru na poziomie, na którym chcemy narzucić limit, a następnie łączymy to z powrotem z naszym zapytaniem „poddrzewa”, aby uzyskać ostateczne pożądane wyniki.
Coś takiego:
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;
Zwróć uwagę na instrukcję limit 25 offset 0
, zakopany pośrodku wewnętrznej selekcji. Ta instrukcja pobierze najnowsze 25 komentarzy „na poziomie głównym”.
[edytuj:może się okazać, że będziesz musiał trochę pobawić się rzeczami, aby uzyskać możliwość porządkowania i/lub ograniczania rzeczy dokładnie tak, jak lubisz. może to obejmować dodawanie informacji w hierarchii, które są zakodowane w parent_path
. na przykład:zamiast /{id}/{id2}/{id3}/
, możesz zdecydować się na dołączenie post_date jako części parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Ułatwiłoby to uzyskanie żądanej kolejności i hierarchii, kosztem konieczności wcześniejszego wypełnienia pola i zarządzania nim w miarę zmian danych]
mam nadzieję, że to pomoże. Powodzenia!