Existem muitos caminhos. Aqui está uma abordagem que eu gosto (e uso regularmente).
O banco de dados
Considere a seguinte estrutura de banco de dados:
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)
);
seus dados ficarão assim:
+-----+-------------------------------------+--------------------------+---------------+
| 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...
É bastante fácil selecionar tudo de uma maneira utilizável:
select id, parent_path, parent_id, comment_text, date_posted
from comments
order by parent_path, date_posted;
ordenando por
parent_path, date_posted
geralmente produzirá resultados na ordem em que você precisar deles ao gerar sua página; mas você vai querer ter certeza de que tem um índice na tabela de comentários que dará suporte adequado a isso - caso contrário, a consulta funciona, mas é muito, muito ineficiente:create index comments_hier_idx on comments (parent_path, date_posted);
Para qualquer comentário único, é fácil obter toda a árvore de comentários filhos desse comentário. Basta adicionar uma cláusula where:
select id, parent_path, parent_id, comment_text, date_posted
from comments
where parent_path like '/1/%'
order by parent_path, date_posted;
a cláusula where adicionada fará uso do mesmo índice que já definimos, então estamos prontos.
Observe que não usamos o
parent_id
ainda. Na verdade, não é estritamente necessário. Mas eu a incluo porque ela nos permite definir uma chave estrangeira tradicional para impor a integridade referencial e implementar exclusões e atualizações em cascata, se quisermos. Restrições de chave estrangeira e regras em cascata estão disponíveis apenas em tabelas INNODB:ALTER TABLE comments ENGINE=InnoDB;
ALTER TABLE comments
ADD FOREIGN KEY ( parent_id ) REFERENCES comments
ON DELETE CASCADE
ON UPDATE CASCADE;
Gerenciando a hierarquia
Para usar essa abordagem, é claro, você terá que definir o
parent_path
corretamente ao inserir cada comentário. E se você mover comentários (o que certamente seria um caso de uso estranho), você terá que atualizar manualmente cada parent_path de cada comentário subordinado ao comentário movido. ... mas essas são coisas bastante fáceis de acompanhar. Se você realmente quiser ser sofisticado (e se seu banco de dados suportar isso), você pode escrever gatilhos para gerenciar o parent_path de forma transparente - deixarei isso como um exercício para o leitor, mas a ideia básica é que os gatilhos de inserção e atualização sejam acionados antes de uma nova inserção ser confirmada. eles subiriam na árvore (usando o
parent_id
relação de chave estrangeira) e reconstruir o valor do parent_path
adequadamente. É até possível quebrar o
parent_path
em uma tabela separada que é gerenciada inteiramente por gatilhos na tabela de comentários, com algumas exibições ou procedimentos armazenados para implementar as várias consultas necessárias. Assim, isola completamente seu código de camada intermediária da necessidade de conhecer ou se preocupar com a mecânica de armazenamento das informações de hierarquia. Claro, nenhuma dessas coisas extravagantes é necessária de forma alguma - geralmente é suficiente apenas soltar o parent_path na tabela e escrever algum código em sua camada intermediária para garantir que ele seja gerenciado adequadamente junto com todos os outros campos você já tem que gerenciar.
Imposição de limites
MySQL (e alguns outros bancos de dados) permite que você selecione "páginas" de dados usando o
LIMIT
cláusula:SELECT * FROM mytable LIMIT 25 OFFSET 0;
Infelizmente, ao lidar com dados hierárquicos como esse, a cláusula LIMIT sozinha não produzirá os resultados desejados.
-- 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;
Em vez disso, precisamos de uma seleção separada no nível em que queremos impor o limite e, em seguida, juntamos isso de volta com nossa consulta "sub-árvore" para fornecer os resultados finais desejados.
Algo assim:
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;
Observe a instrução
limit 25 offset 0
, enterrado no meio do select interno. Essa instrução recuperará os 25 comentários de "nível raiz" mais recentes. [editar:você pode achar que precisa brincar um pouco com as coisas para obter a capacidade de ordenar e/ou limitar as coisas exatamente do jeito que você gosta. isso pode incluir a adição de informações dentro da hierarquia codificada em
parent_path
. por exemplo:em vez de /{id}/{id2}/{id3}/
, você pode decidir incluir o post_date como parte do parent_path:/{id}:{post_date}/{id2}:{post_date2}/{id3}:{post_date3}/
. Isso tornaria muito fácil obter a ordem e a hierarquia desejadas, às custas de ter que preencher o campo antecipadamente e gerenciá-lo à medida que os dados mudam] espero que isso ajude. boa sorte!