Normalmente, existem três tipos de consultas nas hierarquias que causam problemas:
- Devolver todos os ancestrais
- Devolver todos os descendentes
- Retorne todos os filhos (descendentes imediatos).
Aqui está uma pequena tabela que mostra o desempenho de diferentes métodos em
MySQL
: Ancestors Descendants Children Maintainability InnoDB
Adjacency list Good Decent Excellent Easy Yes
Nested sets (classic) Poor Excellent Poor/Excellent Very hard Yes
Nested sets (spatial) Excellent Very good Poor/Excellent Very hard No
Materialized path Excellent Very good Poor/Excellent Hard Yes
Em
children
, poor/excellent
significa que a resposta depende se você está misturando o método com a lista de adjacências, i. e. armazenando o parentID
em cada registro. Para sua tarefa, você precisa de todas as três consultas:
- Todos os ancestrais para mostrar a coisa Terra / Reino Unido / Devon
- Todas as crianças devem mostrar "Destinos na Europa" (os itens)
- Todos os descendentes devem mostrar "Destinos na Europa" (as contagens)
Eu iria por caminhos materializados, já que esse tipo de hierarquia raramente muda (só em caso de guerra, revolta etc).
Crie uma coluna varchar chamada
path
, indexe-o e preencha-o com o valor assim:1:234:6345:45454:
onde os números são chaves primárias dos pais apropriados, na ordem correta (
1
para a Europa, 234
para o Reino Unido etc.) Você também precisará de uma tabela chamada
levels
para manter os números de 1
para 20
(ou qualquer nível máximo de aninhamento desejado). Para selecionar todos os ancestrais:
SELECT pa.*
FROM places p
JOIN levels l
ON SUBSTRING_INDEX(p.path, ':', l.level) <> p.path
JOIN places pa
ON pa.path = CONCAT(SUBSTRING_INDEX(p.path, ':', l.level), ':')
WHERE p.id = @id_of_place_in_devon
Para selecionar todos os filhos e contagens de lugares dentro deles:
SELECT pc.*, COUNT(pp.id)
FROM places p
JOIN places pc
ON pc.parentId = p.id
JOIN places pp
ON pp.path BETWEEN pc.path AND CONCAT(pc.path, ':')
AND pp.id NOT IN
(
SELECT parentId
FROM places
)
WHERE p.id = @id_of_europe
GROUP BY
pc.id