PHP / MySQL Adjacency List Question
I have the following issue : imaging an adjacency list, that's been walked over with a recursion coming out of a sql like this
SELECT * FROM pages as ps ORDER BY COALESCE(child_of,page_id), page_id LIMIT x,y
public static function tree(&$arr, $id = NULL) {
$result = array();
foreach ($arr as $a) {
if ($id == $a['child_of']) {
$a ['children'] = self::tree($arr, $a['page_id']);
$result[] = $a;
}
}
return $result;
}
So far, so good - with another "flattener" I am getting where I need to be. Now , here is the trick , this works on "paginated" results, and what possibly can happen ( and it does ) is that the parent can be in one subset and the child in a different subset. With the recursion above is obvious that the child won't make it to the tree with a missing parent.
Any ideas on how can i solve that? Help is much appreciated开发者_运维知识库.
Hierarchical data in relational tables, don't we all love it?
With your current database layout, you can only solve your problem by either always fetching all nodes, or doing as many JOINS
as you have nesting levels, sort everything properly (your way of sorting only makes this fundamental problem, that you have, a little less important).
Before you ask, No, you should not do this.
The other method you have, is choose an entirely different model to create your hierarchy:
- Nested sets
- ascendant/descendant relationships between all nodes.
See slide 48 et seq. here.
A good read to start with is Hierarchical Data In MySQL (which I used to be able to find on the MySQL.com website, arghh)
Read It?
Here's how it could be done with the adjacency list model. But only for a known fixed amount of nesting (four nesting levels for this example).
I would find out which of my pages are root pages (of the tree). Then select only those with a query. Put the LIMIT x,x
in this select statement.
After that, the following statement: (or something like it)
string query = "
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name IN('ELECTRONICS', '<some other name>');
";
Could return something like this:
+-------------+----------------------+--------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS | TUBE | NULL |
| ELECTRONICS | TELEVISIONS | LCD | NULL |
| ELECTRONICS | TELEVISIONS | PLASMA | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS | NULL |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL |
| etc... | etc... | etc... | |
+-------------+----------------------+--------------+-------+
The trick is to use only the root names of the query with limit (or ID's if you want) in the IN()
statement of the query.
This should perform pretty good still (in theory).
The principle of the above query could also be used to find out how many descendants are in a root of a tree (with a little GROUP BY
and COUNT()
magic ;) Also, you could find out which of your pages are roots with this principle (though I would save that in the tabledata for performance reasons)
If you want a dynamic amount of nesting (for nearly endless scaling), implementing a nested set would be the way to go.
精彩评论