I want to count number of all child nodes under any level of tree structure maintained in a table using adjacency model (parent-child key). Table structure and data looks l
Below is a PHP based solution:
function countChildren($startId) {
$directDescendents = *_query("SELECT id FROM Table WHERE parentid = ?", array( $startId ));
$count = *_num_rows($directDescendents);
while($row = *_fetch_array($directDescendents))
$count += countChildren($row['id']);
return $count;
}
$numChildren = countChildren(2); // Number of Children for 'B'
Replace *_num_rows
and *_fetch_array
with whatever functions for the SQL extension you are using. This won't be as efficient as a pure SQL solution, but it will work. The way I'm querying in the function is assuming bound parameters, but execute the query as you like.
Can be done fairly simply with a non-recursive stored procedure as follows:
Example calls
mysql> call category_hier(1);
+--------------+
| num_children |
+--------------+
| 3 |
+--------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> call category_hier(2);
+--------------+
| num_children |
+--------------+
| 2 |
+--------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Full script
drop table if exists categories;
create table categories
(
cat_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_cat_id smallint unsigned null,
key (parent_cat_id)
)
engine = innodb;
insert into categories (name, parent_cat_id) values
('Location',null),
('Color',null),
('USA',1),
('Illinois',3),
('Chicago',3),
('Black',2),
('Red',2);
drop procedure if exists category_hier;
delimiter #
create procedure category_hier
(
in p_cat_id smallint unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_cat_id smallint unsigned,
cat_id smallint unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier select parent_cat_id, cat_id, v_depth from categories where cat_id = p_cat_id;
create temporary table tmp engine=memory select * from hier;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
while not v_done do
if exists( select 1 from categories c
inner join tmp on c.parent_cat_id = tmp.cat_id and tmp.depth = v_depth) then
insert into hier select c.parent_cat_id, c.cat_id, v_depth + 1 from categories c
inner join tmp on c.parent_cat_id = tmp.cat_id and tmp.depth = v_depth;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
/*
select
c.cat_id,
c.name as category_name,
p.cat_id as parent_cat_id,
p.name as parent_category_name,
hier.depth
from
hier
inner join categories c on hier.cat_id = c.cat_id
left outer join categories p on hier.parent_cat_id = p.cat_id
order by
hier.depth;
*/
select count(*) as num_children from hier where parent_cat_id is not null;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
call category_hier(1);
call category_hier(2);
You can easily adapt this example to suit your requirements.
Hope it helps :)
The way you store your data will not allow for a simple query to get the total child count. But have a look at:
http://en.wikipedia.org/wiki/Nested_set_model
Where a query like this would be possible.