I have just made the update/add/delete part for the "Closure table" way of organizing query hierarchical data that are shown on page 70 in this slideshare: http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back
My database looks like this:
Table Categories:
ID Name
1 Top value
2 Sub value1
Table CategoryTree:
child parent level
1 1 0
2 2 0
2 1 1
However, I have a bit of an issue getting the full tree back as an multidimensional array from a single query.
Here's what I would like to get back:
array (
'topvalue' = array (
'Subvalue',
'Subvalue2',
'Subvalue3)
);
);
Update: Found this link, but I still have a hard time to convert it into an array: http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html
Update2 : I was able to add depths to each of the categories now, if that can be of any help.
Okay, I've written PHP classes that extend the Zend Framework DB table, row, and rowset classes. I've been developing this anyway because I'm speaking at PHP Tek-X in a couple of weeks about hierarchical data models.
I don't want to post all my code to because they implicitly get licensed under Creative Commons if I do that. update: I committed my code to the Zend Framework extras incubator and my presentation is Models for Hierarchical Data with SQL and PHP at slideshare.
I'll describe the solution in pseudocode. I'm using zoological taxonomy as test data, downloaded from ITIS.gov. The table is
longnames
:I've created a closure table for the paths in the hierarchy of taxonomy:
Given the primary key of one node, you can get all its descendants this way:
The join to
closure AS p
is to include each node's parent id.The query makes pretty good use of indexes:
And given that I have 490,032 rows in
longnames
and 4,299,883 rows inclosure
, it runs in pretty good time:Now I post-process the result of the SQL query above, sorting the rows into subsets according to the hierarchy (pseudocode):
I also define classes for Rows and Rowsets. A Rowset is basically an array of rows. A Row contains an associative array of row data, and also contains a Rowset for its children. The children Rowset for a leaf node is empty.
Rows and Rowsets also define methods called
toArrayDeep()
which dump their data content recursively as a plain array.Then I can use the whole system together like this:
The output is as follows:
Re your comment about calculating depth -- or really length of each path.
Assuming you've just inserted a new node to your table that holds the actual nodes (
longnames
in the example above), the id of the new node is returned byLAST_INSERT_ID()
in MySQL or else you can get it somehow.